From 5c1c73d9b640527b0cbb3c954cf5b98108be684d Mon Sep 17 00:00:00 2001 From: An Tao Date: Sat, 30 Nov 2019 08:35:40 +0800 Subject: [PATCH] Add the SecureSSLRedirector plugin (#306) --- CMakeLists.txt | 13 +-- config.example.json | 5 +- drogon_ctl/templates/config.csp | 5 +- lib/inc/drogon/HttpRequest.h | 2 + lib/inc/drogon/drogon.h | 1 + lib/inc/drogon/drogon_callbacks.h | 2 + lib/inc/drogon/plugins/SecureSSLRedirector.h | 67 +++++++++++++ lib/src/HttpRequestImpl.h | 10 ++ lib/src/HttpServer.cc | 2 + lib/src/SecureSSLRedirector.cc | 98 ++++++++++++++++++++ trantor | 2 +- 11 files changed, 194 insertions(+), 13 deletions(-) create mode 100644 lib/inc/drogon/plugins/SecureSSLRedirector.h create mode 100644 lib/src/SecureSSLRedirector.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c544e4d..a702f4f6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,12 +4,6 @@ project(drogon CXX) add_library(${PROJECT_NAME} STATIC) -#if(MSVC) -# target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX) -#else() -# target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -pedantic) -#endif() - set(DROGON_MAJOR_VERSION 1) set(DROGON_MINOR_VERSION 0) set(DROGON_PATCH_VERSION 0) @@ -143,6 +137,7 @@ set(DROGON_SOURCES lib/src/MultiPart.cc lib/src/NotFound.cc lib/src/PluginsManager.cc + lib/src/SecureSSLRedirector.cc lib/src/SessionManager.cc lib/src/SharedLibManager.cc lib/src/StaticFileRouter.cc @@ -281,7 +276,8 @@ endif() target_sources(${PROJECT_NAME} PRIVATE ${DROGON_SOURCES}) -set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD ${DROGON_CXX_STANDARD}) +set_target_properties(${PROJECT_NAME} + PROPERTIES CXX_STANDARD ${DROGON_CXX_STANDARD}) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON) set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF) set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME Drogon) @@ -414,7 +410,8 @@ set(DROGON_UTIL_HEADERS install(FILES ${DROGON_UTIL_HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/utils) -set(DROGON_PLUGIN_HEADERS lib/inc/drogon/plugins/Plugin.h) +set(DROGON_PLUGIN_HEADERS lib/inc/drogon/plugins/Plugin.h + lib/inc/drogon/plugins/SecureSSLRedirector.h) install(FILES ${DROGON_PLUGIN_HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/plugins) diff --git a/config.example.json b/config.example.json index f4fed963..fbe71a76 100644 --- a/config.example.json +++ b/config.example.json @@ -184,13 +184,14 @@ //plugins: Define all plugins running in the application "plugins": [{ //name: The class name of the plugin - //"name": "TestPlugin", + //"name": "drogon::plugin::SecureSSLRedirector", //dependencies: Plugins that the plugin depends on. It can be commented out "dependencies": [], //config: The configuration of the plugin. This json object is the parameter to initialize the plugin. //It can be commented out "config": { - "heartbeat_interval": 2 + "ssl_redirect_exempt": [".*\\.jpg"], + "secure_ssl_host": "localhost:8849" } }], //custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method. diff --git a/drogon_ctl/templates/config.csp b/drogon_ctl/templates/config.csp index ec58afec..b2353a06 100644 --- a/drogon_ctl/templates/config.csp +++ b/drogon_ctl/templates/config.csp @@ -184,13 +184,14 @@ //plugins: Define all plugins running in the application "plugins": [{ //name: The class name of the plugin - //"name": "TestPlugin", + //"name": "drogon::plugin::SecureSSLRedirector", //dependencies: Plugins that the plugin depends on. It can be commented out "dependencies": [], //config: The configuration of the plugin. This json object is the parameter to initialize the plugin. //It can be commented out "config": { - "heartbeat_interval": 2 + "ssl_redirect_exempt": [".*\\.jpg"], + "secure_ssl_host": "localhost:8849" } }], //custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method. diff --git a/lib/inc/drogon/HttpRequest.h b/lib/inc/drogon/HttpRequest.h index 30d3af43..e9fb6700 100644 --- a/lib/inc/drogon/HttpRequest.h +++ b/lib/inc/drogon/HttpRequest.h @@ -359,6 +359,8 @@ class HttpRequest return toRequest(std::forward(obj)); } + virtual bool isOnSecureConnection() const noexcept = 0; + virtual ~HttpRequest() { } diff --git a/lib/inc/drogon/drogon.h b/lib/inc/drogon/drogon.h index e979b0d1..95fa3b02 100644 --- a/lib/inc/drogon/drogon.h +++ b/lib/inc/drogon/drogon.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include diff --git a/lib/inc/drogon/drogon_callbacks.h b/lib/inc/drogon/drogon_callbacks.h index d6b9dc68..2105daff 100644 --- a/lib/inc/drogon/drogon_callbacks.h +++ b/lib/inc/drogon/drogon_callbacks.h @@ -22,6 +22,8 @@ namespace drogon { class HttpResponse; using HttpResponsePtr = std::shared_ptr; +class HttpRequest; +using HttpRequestPtr = std::shared_ptr; using AdviceCallback = std::function; using AdviceChainCallback = std::function; using FilterCallback = std::function; diff --git a/lib/inc/drogon/plugins/SecureSSLRedirector.h b/lib/inc/drogon/plugins/SecureSSLRedirector.h new file mode 100644 index 00000000..a2b1d6ab --- /dev/null +++ b/lib/inc/drogon/plugins/SecureSSLRedirector.h @@ -0,0 +1,67 @@ +/** + * + * drogon_plugin_SecureSSLRedirector.h + * + */ + +#pragma once +#include +#include +#include + +namespace drogon +{ +namespace plugin +{ +/** + * @brief This plugin is used to redirect all non-HTTPS requests to HTTPS + * (except for those URLs matching a regular expression listed in + * the 'ssl_redirect_exempt' list). + * + * The json configuration is as follows: + * + * @code + { + "name": "drogon::plugin::SecureSSLRedirector", + "dependencies": [], + "config": { + "ssl_redirect_exempt": ["^/.*\\.jpg", ...], + "secure_ssl_host": "localhost:8849" + } + } + @endcode + * + * ssl_redirect_exempt: a regular expression (for matching the path of a + * request) list for URLs that don't have to be redirected. + * secure_ssl_host: If this string is not empty, all SSL redirects + * will be directed to this host rather than the originally-requested host. + * + * Enable the plugin by adding the configuration to the list of plugins in the + * configuration file. + * + */ +class SecureSSLRedirector : public drogon::Plugin +{ + public: + SecureSSLRedirector() + { + } + /// This method must be called by drogon to initialize and start the plugin. + /// It must be implemented by the user. + virtual void initAndStart(const Json::Value &config) override; + + /// This method must be called by drogon to shutdown the plugin. + /// It must be implemented by the user. + virtual void shutdown() override; + + private: + HttpResponsePtr redirectingAdvice(const HttpRequestPtr &) const; + HttpResponsePtr redirectToSSL(const HttpRequestPtr &) const; + + std::regex exemptPegex_; + bool regexFlag_{false}; + std::string secureHost_; +}; + +} // namespace plugin +} // namespace drogon diff --git a/lib/src/HttpRequestImpl.h b/lib/src/HttpRequestImpl.h index fc2b4168..0c327173 100644 --- a/lib/src/HttpRequestImpl.h +++ b/lib/src/HttpRequestImpl.h @@ -85,6 +85,10 @@ class HttpRequestImpl : public HttpRequest } bool setMethod(const char *start, const char *end); + void setSecure(bool secure) + { + isOnSecureConnection_ = secure; + } virtual void setMethod(const HttpMethod method) override { @@ -404,6 +408,11 @@ class HttpRequestImpl : public HttpRequest { return keepAlive_; } + virtual bool isOnSecureConnection() const noexcept override + { + return isOnSecureConnection_; + } + ~HttpRequestImpl(); protected: @@ -450,6 +459,7 @@ class HttpRequestImpl : public HttpRequest std::unique_ptr cacheFilePtr_; std::string expect_; bool keepAlive_{true}; + bool isOnSecureConnection_{false}; protected: std::string content_; diff --git a/lib/src/HttpServer.cc b/lib/src/HttpServer.cc index 25803e0f..3ff3793b 100644 --- a/lib/src/HttpServer.cc +++ b/lib/src/HttpServer.cc @@ -203,6 +203,8 @@ void HttpServer::onMessage(const TcpConnectionPtr &conn, MsgBuffer *buf) requestParser->requestImpl()->setLocalAddr(conn->localAddr()); requestParser->requestImpl()->setCreationDate( trantor::Date::date()); + requestParser->requestImpl()->setSecure( + conn->isSSLConnection()); if (requestParser->firstReq() && isWebSocket(requestParser->requestImpl())) { diff --git a/lib/src/SecureSSLRedirector.cc b/lib/src/SecureSSLRedirector.cc new file mode 100644 index 00000000..4dcfcc68 --- /dev/null +++ b/lib/src/SecureSSLRedirector.cc @@ -0,0 +1,98 @@ +/** + * + * drogon_plugin_SecureSSLRedirector.cc + * + */ +#include +#include +#include + +using namespace drogon; +using namespace drogon::plugin; + +void SecureSSLRedirector::initAndStart(const Json::Value &config) +{ + if (config.isMember("ssl_redirect_exempt") && + config["ssl_redirect_exempt"].isArray()) + { + std::string regexString; + for (auto &exempt : config["ssl_redirect_exempt"]) + { + assert(exempt.isString()); + regexString.append("(").append(exempt.asString()).append(")|"); + } + if (!regexString.empty()) + { + regexString.resize(regexString.length() - 1); + exemptPegex_ = std::regex(regexString); + regexFlag_ = true; + } + } + secureHost_ = config.get("secure_ssl_host", "").asString(); + app().registerSyncAdvice([this](const HttpRequestPtr &req) { + return this->redirectingAdvice(req); + }); +} + +void SecureSSLRedirector::shutdown() +{ + /// Shutdown the plugin +} + +HttpResponsePtr SecureSSLRedirector::redirectingAdvice( + const HttpRequestPtr &req) const +{ + if (req->isOnSecureConnection()) + { + return HttpResponsePtr{}; + } + else if (regexFlag_) + { + std::smatch regexResult; + if (std::regex_match(req->path(), regexResult, exemptPegex_)) + { + return HttpResponsePtr{}; + } + else + { + return redirectToSSL(req); + } + } + else + { + return redirectToSSL(req); + } +} + +HttpResponsePtr SecureSSLRedirector::redirectToSSL( + const HttpRequestPtr &req) const +{ + if (!secureHost_.empty()) + { + static std::string urlPrefix{"https://" + secureHost_}; + std::string query{urlPrefix + req->path()}; + if (!req->query().empty()) + { + query += "?" + req->query(); + } + return HttpResponse::newRedirectionResponse(query); + } + else + { + const auto &host = req->getHeader("host"); + if (!host.empty()) + { + std::string query{"https://" + host}; + query += req->path(); + if (!req->query().empty()) + { + query += "?" + req->query(); + } + return HttpResponse::newRedirectionResponse(query); + } + else + { + return HttpResponse::newNotFoundResponse(); + } + } +} \ No newline at end of file diff --git a/trantor b/trantor index 374acaa8..f36e2e46 160000 --- a/trantor +++ b/trantor @@ -1 +1 @@ -Subproject commit 374acaa8aa39aa7a1a2c2d92dbb2c0d940e0f33b +Subproject commit f36e2e46a7bfa404e730ec36cf223c75f6fbe34a