diff options
author | Ed Tanous <edtanous@google.com> | 2023-04-06 23:10:02 +0300 |
---|---|---|
committer | Ed Tanous <ed@tanous.net> | 2023-06-09 22:13:40 +0300 |
commit | 08bbe1199f02d09f908cd3adcf4329e4bd67fd52 (patch) | |
tree | 9824a615569928f65523f13a876a0def301562dd /http | |
parent | b90d14f220cba6de26dcf8749b2f8df062487d72 (diff) | |
download | bmcweb-08bbe1199f02d09f908cd3adcf4329e4bd67fd52.tar.xz |
Break up router into separate files
The router is a giant behemoth. Start breaking it down into pieces.
Tested: Redfish service validator passes.
Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I9d04f53a58ffce3ecbd88dded1aa6e9648d2a762
Diffstat (limited to 'http')
-rw-r--r-- | http/routing.hpp | 767 | ||||
-rw-r--r-- | http/routing/baserule.hpp | 98 | ||||
-rw-r--r-- | http/routing/dynamicrule.hpp | 233 | ||||
-rw-r--r-- | http/routing/ruleparametertraits.hpp | 94 | ||||
-rw-r--r-- | http/routing/sserule.hpp | 79 | ||||
-rw-r--r-- | http/routing/taggedrule.hpp | 68 | ||||
-rw-r--r-- | http/routing/websocketrule.hpp | 108 |
7 files changed, 686 insertions, 761 deletions
diff --git a/http/routing.hpp b/http/routing.hpp index f3bcfbb065..97daef1fcf 100644 --- a/http/routing.hpp +++ b/http/routing.hpp @@ -2,13 +2,18 @@ #include "async_resp.hpp" #include "common.hpp" +#include "dbus_privileges.hpp" #include "dbus_utility.hpp" #include "error_messages.hpp" #include "http_request.hpp" #include "http_response.hpp" #include "logging.hpp" #include "privileges.hpp" -#include "server_sent_event.hpp" +#include "routing/baserule.hpp" +#include "routing/dynamicrule.hpp" +#include "routing/sserule.hpp" +#include "routing/taggedrule.hpp" +#include "routing/websocketrule.hpp" #include "sessions.hpp" #include "utility.hpp" #include "utils/dbus_utils.hpp" @@ -33,601 +38,6 @@ namespace crow { -class BaseRule -{ - public: - explicit BaseRule(const std::string& thisRule) : rule(thisRule) {} - - virtual ~BaseRule() = default; - - BaseRule(const BaseRule&) = delete; - BaseRule(BaseRule&&) = delete; - BaseRule& operator=(const BaseRule&) = delete; - BaseRule& operator=(const BaseRule&&) = delete; - - virtual void validate() = 0; - std::unique_ptr<BaseRule> upgrade() - { - if (ruleToUpgrade) - { - return std::move(ruleToUpgrade); - } - return {}; - } - - virtual void handle(const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>&, - const std::vector<std::string>&) = 0; -#ifndef BMCWEB_ENABLE_SSL - virtual void - handleUpgrade(const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - boost::asio::ip::tcp::socket&& /*adaptor*/) - { - asyncResp->res.result(boost::beast::http::status::not_found); - } -#else - virtual void handleUpgrade( - const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& /*adaptor*/) - { - asyncResp->res.result(boost::beast::http::status::not_found); - } -#endif - - size_t getMethods() const - { - return methodsBitfield; - } - - bool checkPrivileges(const redfish::Privileges& userPrivileges) - { - // If there are no privileges assigned, assume no privileges - // required - if (privilegesSet.empty()) - { - return true; - } - - for (const redfish::Privileges& requiredPrivileges : privilegesSet) - { - if (userPrivileges.isSupersetOf(requiredPrivileges)) - { - return true; - } - } - return false; - } - - size_t methodsBitfield{1 << static_cast<size_t>(HttpVerb::Get)}; - static_assert(std::numeric_limits<decltype(methodsBitfield)>::digits > - methodNotAllowedIndex, - "Not enough bits to store bitfield"); - - std::vector<redfish::Privileges> privilegesSet; - - std::string rule; - - std::unique_ptr<BaseRule> ruleToUpgrade; - - friend class Router; - template <typename T> - friend struct RuleParameterTraits; -}; - -namespace detail -{ -namespace routing_handler_call_helper -{ -template <typename T, int Pos> -struct CallPair -{ - using type = T; - static const int pos = Pos; -}; - -template <typename H1> -struct CallParams -{ - H1& handler; - const std::vector<std::string>& params; - const Request& req; - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp; -}; - -template <typename F, int NString, typename S1, typename S2> -struct Call -{}; - -template <typename F, int NString, typename... Args1, typename... Args2> -struct Call<F, NString, black_magic::S<std::string, Args1...>, - black_magic::S<Args2...>> -{ - void operator()(F cparams) - { - using pushed = typename black_magic::S<Args2...>::template push_back< - CallPair<std::string, NString>>; - Call<F, NString + 1, black_magic::S<Args1...>, pushed>()(cparams); - } -}; - -template <typename F, int NString, typename... Args1> -struct Call<F, NString, black_magic::S<>, black_magic::S<Args1...>> -{ - void operator()(F cparams) - { - cparams.handler(cparams.req, cparams.asyncResp, - cparams.params[Args1::pos]...); - } -}; - -template <typename Func, typename... ArgsWrapped> -struct Wrapped -{ - template <typename... Args> - void set( - Func f, - typename std::enable_if< - !std::is_same< - typename std::tuple_element<0, std::tuple<Args..., void>>::type, - const Request&>::value, - int>::type /*enable*/ - = 0) - { - handler = [f = std::forward<Func>(f)]( - const Request&, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - Args... args) { asyncResp->res.result(f(args...)); }; - } - - template <typename Req, typename... Args> - struct ReqHandlerWrapper - { - explicit ReqHandlerWrapper(Func fIn) : f(std::move(fIn)) {} - - void operator()(const Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - Args... args) - { - asyncResp->res.result(f(req, args...)); - } - - Func f; - }; - - template <typename... Args> - void set( - Func f, - typename std::enable_if< - std::is_same< - typename std::tuple_element<0, std::tuple<Args..., void>>::type, - const Request&>::value && - !std::is_same<typename std::tuple_element< - 1, std::tuple<Args..., void, void>>::type, - const std::shared_ptr<bmcweb::AsyncResp>&>::value, - int>::type /*enable*/ - = 0) - { - handler = ReqHandlerWrapper<Args...>(std::move(f)); - /*handler = ( - [f = std::move(f)] - (const Request& req, Response& res, Args... args){ - res.result(f(req, args...)); - res.end(); - });*/ - } - - template <typename... Args> - void set( - Func f, - typename std::enable_if< - std::is_same< - typename std::tuple_element<0, std::tuple<Args..., void>>::type, - const Request&>::value && - std::is_same<typename std::tuple_element< - 1, std::tuple<Args..., void, void>>::type, - const std::shared_ptr<bmcweb::AsyncResp>&>::value, - int>::type /*enable*/ - = 0) - { - handler = std::move(f); - } - - template <typename... Args> - struct HandlerTypeHelper - { - using type = std::function<void( - const crow::Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; - using args_type = black_magic::S<Args...>; - }; - - template <typename... Args> - struct HandlerTypeHelper<const Request&, Args...> - { - using type = std::function<void( - const crow::Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; - using args_type = black_magic::S<Args...>; - }; - - template <typename... Args> - struct HandlerTypeHelper<const Request&, - const std::shared_ptr<bmcweb::AsyncResp>&, Args...> - { - using type = std::function<void( - const crow::Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; - using args_type = black_magic::S<Args...>; - }; - - typename HandlerTypeHelper<ArgsWrapped...>::type handler; - - void operator()(const Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::vector<std::string>& params) - { - detail::routing_handler_call_helper::Call< - detail::routing_handler_call_helper::CallParams<decltype(handler)>, - 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type, - black_magic::S<>>()( - detail::routing_handler_call_helper::CallParams<decltype(handler)>{ - handler, params, req, asyncResp}); - } -}; -} // namespace routing_handler_call_helper -} // namespace detail - -class WebSocketRule : public BaseRule -{ - using self_t = WebSocketRule; - - public: - explicit WebSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) {} - - void validate() override {} - - void handle(const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::vector<std::string>& /*params*/) override - { - asyncResp->res.result(boost::beast::http::status::not_found); - } - -#ifndef BMCWEB_ENABLE_SSL - void handleUpgrade(const Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, - boost::asio::ip::tcp::socket&& adaptor) override - { - BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; - std::shared_ptr< - crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>> - myConnection = std::make_shared< - crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>( - req, req.url(), std::move(adaptor), openHandler, messageHandler, - messageExHandler, closeHandler, errorHandler); - myConnection->start(); - } -#else - void handleUpgrade(const Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& - adaptor) override - { - BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; - std::shared_ptr<crow::websocket::ConnectionImpl< - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> - myConnection = std::make_shared<crow::websocket::ConnectionImpl< - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( - req, req.url(), std::move(adaptor), openHandler, messageHandler, - messageExHandler, closeHandler, errorHandler); - myConnection->start(); - } -#endif - - template <typename Func> - self_t& onopen(Func f) - { - openHandler = f; - return *this; - } - - template <typename Func> - self_t& onmessage(Func f) - { - messageHandler = f; - return *this; - } - - template <typename Func> - self_t& onmessageex(Func f) - { - messageExHandler = f; - return *this; - } - - template <typename Func> - self_t& onclose(Func f) - { - closeHandler = f; - return *this; - } - - template <typename Func> - self_t& onerror(Func f) - { - errorHandler = f; - return *this; - } - - protected: - std::function<void(crow::websocket::Connection&)> openHandler; - std::function<void(crow::websocket::Connection&, const std::string&, bool)> - messageHandler; - std::function<void(crow::websocket::Connection&, std::string_view, - crow::websocket::MessageType type, - std::function<void()>&& whenComplete)> - messageExHandler; - std::function<void(crow::websocket::Connection&, const std::string&)> - closeHandler; - std::function<void(crow::websocket::Connection&)> errorHandler; -}; - -class SseSocketRule : public BaseRule -{ - using self_t = SseSocketRule; - - public: - explicit SseSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) {} - - void validate() override {} - - void handle(const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::vector<std::string>& /*params*/) override - { - asyncResp->res.result(boost::beast::http::status::not_found); - } - -#ifndef BMCWEB_ENABLE_SSL - void handleUpgrade(const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, - boost::asio::ip::tcp::socket&& adaptor) override - { - std::shared_ptr< - crow::sse_socket::ConnectionImpl<boost::asio::ip::tcp::socket>> - myConnection = std::make_shared< - crow::sse_socket::ConnectionImpl<boost::asio::ip::tcp::socket>>( - std::move(adaptor), openHandler, closeHandler); - myConnection->start(); - } -#else - void handleUpgrade(const Request& /*req*/, - const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& - adaptor) override - { - std::shared_ptr<crow::sse_socket::ConnectionImpl< - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> - myConnection = std::make_shared<crow::sse_socket::ConnectionImpl< - boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( - std::move(adaptor), openHandler, closeHandler); - myConnection->start(); - } -#endif - - template <typename Func> - self_t& onopen(Func f) - { - openHandler = f; - return *this; - } - - template <typename Func> - self_t& onclose(Func f) - { - closeHandler = f; - return *this; - } - - private: - std::function<void(crow::sse_socket::Connection&)> openHandler; - std::function<void(crow::sse_socket::Connection&)> closeHandler; -}; - -template <typename T> -struct RuleParameterTraits -{ - using self_t = T; - WebSocketRule& websocket() - { - self_t* self = static_cast<self_t*>(this); - WebSocketRule* p = new WebSocketRule(self->rule); - p->privilegesSet = self->privilegesSet; - self->ruleToUpgrade.reset(p); - return *p; - } - - SseSocketRule& serverSentEvent() - { - self_t* self = static_cast<self_t*>(this); - SseSocketRule* p = new SseSocketRule(self->rule); - self->ruleToUpgrade.reset(p); - return *p; - } - - self_t& methods(boost::beast::http::verb method) - { - self_t* self = static_cast<self_t*>(this); - std::optional<HttpVerb> verb = httpVerbFromBoost(method); - if (verb) - { - self->methodsBitfield = 1U << static_cast<size_t>(*verb); - } - return *self; - } - - template <typename... MethodArgs> - self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) - { - self_t* self = static_cast<self_t*>(this); - methods(argsMethod...); - std::optional<HttpVerb> verb = httpVerbFromBoost(method); - if (verb) - { - self->methodsBitfield |= 1U << static_cast<size_t>(*verb); - } - return *self; - } - - self_t& notFound() - { - self_t* self = static_cast<self_t*>(this); - self->methodsBitfield = 1U << notFoundIndex; - return *self; - } - - self_t& methodNotAllowed() - { - self_t* self = static_cast<self_t*>(this); - self->methodsBitfield = 1U << methodNotAllowedIndex; - return *self; - } - - self_t& privileges( - const std::initializer_list<std::initializer_list<const char*>>& p) - { - self_t* self = static_cast<self_t*>(this); - for (const std::initializer_list<const char*>& privilege : p) - { - self->privilegesSet.emplace_back(privilege); - } - return *self; - } - - template <size_t N, typename... MethodArgs> - self_t& privileges(const std::array<redfish::Privileges, N>& p) - { - self_t* self = static_cast<self_t*>(this); - for (const redfish::Privileges& privilege : p) - { - self->privilegesSet.emplace_back(privilege); - } - return *self; - } -}; - -class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> -{ - public: - explicit DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) {} - - void validate() override - { - if (!erasedHandler) - { - throw std::runtime_error("no handler for url " + rule); - } - } - - void handle(const Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::vector<std::string>& params) override - { - erasedHandler(req, asyncResp, params); - } - - template <typename Func> - void operator()(Func f) - { - using boost::callable_traits::args_t; - constexpr size_t arity = std::tuple_size<args_t<Func>>::value; - constexpr auto is = std::make_integer_sequence<unsigned, arity>{}; - erasedHandler = wrap(std::move(f), is); - } - - // enable_if Arg1 == request && Arg2 == Response - // enable_if Arg1 == request && Arg2 != response - // enable_if Arg1 != request - - template <typename Func, unsigned... Indices> - std::function<void(const Request&, - const std::shared_ptr<bmcweb::AsyncResp>&, - const std::vector<std::string>&)> - wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/) - { - using function_t = crow::utility::FunctionTraits<Func>; - - auto ret = detail::routing_handler_call_helper::Wrapped< - Func, typename function_t::template arg<Indices>...>(); - ret.template set<typename function_t::template arg<Indices>...>( - std::move(f)); - return ret; - } - - private: - std::function<void(const Request&, - const std::shared_ptr<bmcweb::AsyncResp>&, - const std::vector<std::string>&)> - erasedHandler; -}; - -template <typename... Args> -class TaggedRule : - public BaseRule, - public RuleParameterTraits<TaggedRule<Args...>> -{ - public: - using self_t = TaggedRule<Args...>; - - explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) {} - - void validate() override - { - if (!handler) - { - throw std::runtime_error("no handler for url " + rule); - } - } - - template <typename Func> - void operator()(Func&& f) - { - static_assert( - black_magic::CallHelper< - Func, black_magic::S<crow::Request, - std::shared_ptr<bmcweb::AsyncResp>&, - Args...>>::value, - "Handler type is mismatched with URL parameters"); - static_assert( - std::is_same< - void, - decltype(f(std::declval<crow::Request>(), - std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(), - std::declval<Args>()...))>::value, - "Handler function with response argument should have void return type"); - - handler = std::forward<Func>(f); - } - - void handle(const Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const std::vector<std::string>& params) override - { - detail::routing_handler_call_helper::Call< - detail::routing_handler_call_helper::CallParams<decltype(handler)>, - 0, black_magic::S<Args...>, black_magic::S<>>()( - detail::routing_handler_call_helper::CallParams<decltype(handler)>{ - handler, params, req, asyncResp}); - } - - private: - std::function<void(const crow::Request&, - const std::shared_ptr<bmcweb::AsyncResp>&, Args...)> - handler; -}; - class Trie { public: @@ -1100,171 +510,6 @@ class Router return findRoute; } - // Populate session with user information. - static bool - populateUserInfo(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const dbus::utility::DBusPropertiesMap& userInfoMap) - { - const std::string* userRolePtr = nullptr; - const bool* remoteUser = nullptr; - const bool* passwordExpired = nullptr; - const std::vector<std::string>* userGroups = nullptr; - - const bool success = sdbusplus::unpackPropertiesNoThrow( - redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, - "UserPrivilege", userRolePtr, "RemoteUser", remoteUser, - "UserPasswordExpired", passwordExpired, "UserGroups", userGroups); - - if (!success) - { - BMCWEB_LOG_ERROR << "Failed to unpack user properties."; - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return false; - } - - if (userRolePtr != nullptr) - { - req.session->userRole = *userRolePtr; - BMCWEB_LOG_DEBUG << "userName = " << req.session->username - << " userRole = " << *userRolePtr; - } - - if (remoteUser == nullptr) - { - BMCWEB_LOG_ERROR << "RemoteUser property missing or wrong type"; - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return false; - } - bool expired = false; - if (passwordExpired == nullptr) - { - if (!*remoteUser) - { - BMCWEB_LOG_ERROR - << "UserPasswordExpired property is expected for" - " local user but is missing or wrong type"; - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return false; - } - } - else - { - expired = *passwordExpired; - } - - // Set isConfigureSelfOnly based on D-Bus results. This - // ignores the results from both pamAuthenticateUser and the - // value from any previous use of this session. - req.session->isConfigureSelfOnly = expired; - - if (userGroups != nullptr) - { - // Populate session with user groups. - for (const auto& userGroup : *userGroups) - { - req.session->userGroups.emplace_back(userGroup); - } - } - - return true; - } - - static bool - isUserPrivileged(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - BaseRule& rule) - { - // Get the user's privileges from the role - redfish::Privileges userPrivileges = - redfish::getUserPrivileges(*req.session); - - // Modify privileges if isConfigureSelfOnly. - if (req.session->isConfigureSelfOnly) - { - // Remove all privileges except ConfigureSelf - userPrivileges = userPrivileges.intersection( - redfish::Privileges{"ConfigureSelf"}); - BMCWEB_LOG_DEBUG << "Operation limited to ConfigureSelf"; - } - - if (!rule.checkPrivileges(userPrivileges)) - { - asyncResp->res.result(boost::beast::http::status::forbidden); - if (req.session->isConfigureSelfOnly) - { - redfish::messages::passwordChangeRequired( - asyncResp->res, - boost::urls::format( - "/redfish/v1/AccountService/Accounts/{}", - req.session->username)); - } - return false; - } - - req.userRole = req.session->userRole; - return true; - } - - template <typename CallbackFn> - void afterGetUserInfo(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - BaseRule& rule, CallbackFn&& callback, - const boost::system::error_code& ec, - const dbus::utility::DBusPropertiesMap& userInfoMap) - { - if (ec) - { - BMCWEB_LOG_ERROR << "GetUserInfo failed..."; - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return; - } - - if (!populateUserInfo(req, asyncResp, userInfoMap)) - { - BMCWEB_LOG_ERROR << "Failed to populate user information"; - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return; - } - - if (!Router::isUserPrivileged(req, asyncResp, rule)) - { - // User is not privileged - BMCWEB_LOG_ERROR << "Insufficient Privilege"; - asyncResp->res.result(boost::beast::http::status::forbidden); - return; - } - callback(req); - } - - template <typename CallbackFn> - void validatePrivilege(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - BaseRule& rule, CallbackFn&& callback) - { - if (req.session == nullptr) - { - return; - } - std::string username = req.session->username; - crow::connections::systemBus->async_method_call( - [this, &req, asyncResp, &rule, - callback(std::forward<CallbackFn>(callback))]( - const boost::system::error_code& ec, - const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { - afterGetUserInfo(req, asyncResp, rule, - std::forward<CallbackFn>(callback), ec, - userInfoMap); - }, - "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", - "xyz.openbmc_project.User.Manager", "GetUserInfo", username); - } - template <typename Adaptor> void handleUpgrade(Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, diff --git a/http/routing/baserule.hpp b/http/routing/baserule.hpp new file mode 100644 index 0000000000..0913020935 --- /dev/null +++ b/http/routing/baserule.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include "async_resp.hpp" +#include "http_request.hpp" +#include "privileges.hpp" +#include "verb.hpp" + +#include <boost/beast/ssl/ssl_stream.hpp> + +#include <memory> +#include <string> + +namespace crow +{ +class BaseRule +{ + public: + explicit BaseRule(const std::string& thisRule) : rule(thisRule) {} + + virtual ~BaseRule() = default; + + BaseRule(const BaseRule&) = delete; + BaseRule(BaseRule&&) = delete; + BaseRule& operator=(const BaseRule&) = delete; + BaseRule& operator=(const BaseRule&&) = delete; + + virtual void validate() = 0; + std::unique_ptr<BaseRule> upgrade() + { + if (ruleToUpgrade) + { + return std::move(ruleToUpgrade); + } + return {}; + } + + virtual void handle(const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>&, + const std::vector<std::string>&) = 0; +#ifndef BMCWEB_ENABLE_SSL + virtual void + handleUpgrade(const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + boost::asio::ip::tcp::socket&& /*adaptor*/) + { + asyncResp->res.result(boost::beast::http::status::not_found); + } +#else + virtual void handleUpgrade( + const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& /*adaptor*/) + { + asyncResp->res.result(boost::beast::http::status::not_found); + } +#endif + + size_t getMethods() const + { + return methodsBitfield; + } + + bool checkPrivileges(const redfish::Privileges& userPrivileges) + { + // If there are no privileges assigned, assume no privileges + // required + if (privilegesSet.empty()) + { + return true; + } + + for (const redfish::Privileges& requiredPrivileges : privilegesSet) + { + if (userPrivileges.isSupersetOf(requiredPrivileges)) + { + return true; + } + } + return false; + } + + size_t methodsBitfield{1 << static_cast<size_t>(HttpVerb::Get)}; + static_assert(std::numeric_limits<decltype(methodsBitfield)>::digits > + methodNotAllowedIndex, + "Not enough bits to store bitfield"); + + std::vector<redfish::Privileges> privilegesSet; + + std::string rule; + + std::unique_ptr<BaseRule> ruleToUpgrade; + + friend class Router; + template <typename T> + friend struct RuleParameterTraits; +}; + +} // namespace crow diff --git a/http/routing/dynamicrule.hpp b/http/routing/dynamicrule.hpp new file mode 100644 index 0000000000..746765cf78 --- /dev/null +++ b/http/routing/dynamicrule.hpp @@ -0,0 +1,233 @@ +#pragma once +#include "baserule.hpp" +#include "ruleparametertraits.hpp" +#include "websocket.hpp" + +#include <boost/beast/http/verb.hpp> + +#include <functional> +#include <limits> +#include <string> +#include <type_traits> + +namespace crow +{ +namespace detail +{ +namespace routing_handler_call_helper +{ +template <typename T, int Pos> +struct CallPair +{ + using type = T; + static const int pos = Pos; +}; + +template <typename H1> +struct CallParams +{ + H1& handler; + const std::vector<std::string>& params; + const Request& req; + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp; +}; + +template <typename F, int NString, typename S1, typename S2> +struct Call +{}; + +template <typename F, int NString, typename... Args1, typename... Args2> +struct Call<F, NString, black_magic::S<std::string, Args1...>, + black_magic::S<Args2...>> +{ + void operator()(F cparams) + { + using pushed = typename black_magic::S<Args2...>::template push_back< + CallPair<std::string, NString>>; + Call<F, NString + 1, black_magic::S<Args1...>, pushed>()(cparams); + } +}; + +template <typename F, int NString, typename... Args1> +struct Call<F, NString, black_magic::S<>, black_magic::S<Args1...>> +{ + void operator()(F cparams) + { + cparams.handler(cparams.req, cparams.asyncResp, + cparams.params[Args1::pos]...); + } +}; + +template <typename Func, typename... ArgsWrapped> +struct Wrapped +{ + template <typename... Args> + void set( + Func f, + typename std::enable_if< + !std::is_same< + typename std::tuple_element<0, std::tuple<Args..., void>>::type, + const Request&>::value, + int>::type /*enable*/ + = 0) + { + handler = [f = std::forward<Func>(f)]( + const Request&, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + Args... args) { asyncResp->res.result(f(args...)); }; + } + + template <typename Req, typename... Args> + struct ReqHandlerWrapper + { + explicit ReqHandlerWrapper(Func fIn) : f(std::move(fIn)) {} + + void operator()(const Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + Args... args) + { + asyncResp->res.result(f(req, args...)); + } + + Func f; + }; + + template <typename... Args> + void set( + Func f, + typename std::enable_if< + std::is_same< + typename std::tuple_element<0, std::tuple<Args..., void>>::type, + const Request&>::value && + !std::is_same<typename std::tuple_element< + 1, std::tuple<Args..., void, void>>::type, + const std::shared_ptr<bmcweb::AsyncResp>&>::value, + int>::type /*enable*/ + = 0) + { + handler = ReqHandlerWrapper<Args...>(std::move(f)); + /*handler = ( + [f = std::move(f)] + (const Request& req, Response& res, Args... args){ + res.result(f(req, args...)); + res.end(); + });*/ + } + + template <typename... Args> + void set( + Func f, + typename std::enable_if< + std::is_same< + typename std::tuple_element<0, std::tuple<Args..., void>>::type, + const Request&>::value && + std::is_same<typename std::tuple_element< + 1, std::tuple<Args..., void, void>>::type, + const std::shared_ptr<bmcweb::AsyncResp>&>::value, + int>::type /*enable*/ + = 0) + { + handler = std::move(f); + } + + template <typename... Args> + struct HandlerTypeHelper + { + using type = std::function<void( + const crow::Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; + using args_type = black_magic::S<Args...>; + }; + + template <typename... Args> + struct HandlerTypeHelper<const Request&, Args...> + { + using type = std::function<void( + const crow::Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; + using args_type = black_magic::S<Args...>; + }; + + template <typename... Args> + struct HandlerTypeHelper<const Request&, + const std::shared_ptr<bmcweb::AsyncResp>&, Args...> + { + using type = std::function<void( + const crow::Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>&, Args...)>; + using args_type = black_magic::S<Args...>; + }; + + typename HandlerTypeHelper<ArgsWrapped...>::type handler; + + void operator()(const Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::vector<std::string>& params) + { + detail::routing_handler_call_helper::Call< + detail::routing_handler_call_helper::CallParams<decltype(handler)>, + 0, typename HandlerTypeHelper<ArgsWrapped...>::args_type, + black_magic::S<>>()( + detail::routing_handler_call_helper::CallParams<decltype(handler)>{ + handler, params, req, asyncResp}); + } +}; +} // namespace routing_handler_call_helper +} // namespace detail + +class DynamicRule : public BaseRule, public RuleParameterTraits<DynamicRule> +{ + public: + explicit DynamicRule(const std::string& ruleIn) : BaseRule(ruleIn) {} + + void validate() override + { + if (!erasedHandler) + { + throw std::runtime_error("no handler for url " + rule); + } + } + + void handle(const Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::vector<std::string>& params) override + { + erasedHandler(req, asyncResp, params); + } + + template <typename Func> + void operator()(Func f) + { + using boost::callable_traits::args_t; + constexpr size_t arity = std::tuple_size<args_t<Func>>::value; + constexpr auto is = std::make_integer_sequence<unsigned, arity>{}; + erasedHandler = wrap(std::move(f), is); + } + + // enable_if Arg1 == request && Arg2 == Response + // enable_if Arg1 == request && Arg2 != response + // enable_if Arg1 != request + + template <typename Func, unsigned... Indices> + std::function<void(const Request&, + const std::shared_ptr<bmcweb::AsyncResp>&, + const std::vector<std::string>&)> + wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/) + { + using function_t = crow::utility::FunctionTraits<Func>; + + auto ret = detail::routing_handler_call_helper::Wrapped< + Func, typename function_t::template arg<Indices>...>(); + ret.template set<typename function_t::template arg<Indices>...>( + std::move(f)); + return ret; + } + + private: + std::function<void(const Request&, + const std::shared_ptr<bmcweb::AsyncResp>&, + const std::vector<std::string>&)> + erasedHandler; +}; + +} // namespace crow diff --git a/http/routing/ruleparametertraits.hpp b/http/routing/ruleparametertraits.hpp new file mode 100644 index 0000000000..bb3f563301 --- /dev/null +++ b/http/routing/ruleparametertraits.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include "sserule.hpp" +#include "websocketrule.hpp" + +#include <boost/beast/http/verb.hpp> + +#include <initializer_list> +#include <optional> + +namespace crow +{ +template <typename T> +struct RuleParameterTraits +{ + using self_t = T; + WebSocketRule& websocket() + { + self_t* self = static_cast<self_t*>(this); + WebSocketRule* p = new WebSocketRule(self->rule); + p->privilegesSet = self->privilegesSet; + self->ruleToUpgrade.reset(p); + return *p; + } + + SseSocketRule& serverSentEvent() + { + self_t* self = static_cast<self_t*>(this); + SseSocketRule* p = new SseSocketRule(self->rule); + self->ruleToUpgrade.reset(p); + return *p; + } + + self_t& methods(boost::beast::http::verb method) + { + self_t* self = static_cast<self_t*>(this); + std::optional<HttpVerb> verb = httpVerbFromBoost(method); + if (verb) + { + self->methodsBitfield = 1U << static_cast<size_t>(*verb); + } + return *self; + } + + template <typename... MethodArgs> + self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) + { + self_t* self = static_cast<self_t*>(this); + methods(argsMethod...); + std::optional<HttpVerb> verb = httpVerbFromBoost(method); + if (verb) + { + self->methodsBitfield |= 1U << static_cast<size_t>(*verb); + } + return *self; + } + + self_t& notFound() + { + self_t* self = static_cast<self_t*>(this); + self->methodsBitfield = 1U << notFoundIndex; + return *self; + } + + self_t& methodNotAllowed() + { + self_t* self = static_cast<self_t*>(this); + self->methodsBitfield = 1U << methodNotAllowedIndex; + return *self; + } + + self_t& privileges( + const std::initializer_list<std::initializer_list<const char*>>& p) + { + self_t* self = static_cast<self_t*>(this); + for (const std::initializer_list<const char*>& privilege : p) + { + self->privilegesSet.emplace_back(privilege); + } + return *self; + } + + template <size_t N, typename... MethodArgs> + self_t& privileges(const std::array<redfish::Privileges, N>& p) + { + self_t* self = static_cast<self_t*>(this); + for (const redfish::Privileges& privilege : p) + { + self->privilegesSet.emplace_back(privilege); + } + return *self; + } +}; +} // namespace crow diff --git a/http/routing/sserule.hpp b/http/routing/sserule.hpp new file mode 100644 index 0000000000..c0a4e504b3 --- /dev/null +++ b/http/routing/sserule.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "baserule.hpp" +#include "http_request.hpp" +#include "http_response.hpp" +#include "server_sent_event.hpp" + +#include <boost/beast/http/verb.hpp> + +#include <functional> +#include <memory> +#include <string> + +namespace crow +{ + +class SseSocketRule : public BaseRule +{ + using self_t = SseSocketRule; + + public: + explicit SseSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) {} + + void validate() override {} + + void handle(const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::vector<std::string>& /*params*/) override + { + asyncResp->res.result(boost::beast::http::status::not_found); + } + +#ifndef BMCWEB_ENABLE_SSL + void handleUpgrade(const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, + boost::asio::ip::tcp::socket&& adaptor) override + { + std::shared_ptr< + crow::sse_socket::ConnectionImpl<boost::asio::ip::tcp::socket>> + myConnection = std::make_shared< + crow::sse_socket::ConnectionImpl<boost::asio::ip::tcp::socket>>( + std::move(adaptor), openHandler, closeHandler); + myConnection->start(); + } +#else + void handleUpgrade(const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& + adaptor) override + { + std::shared_ptr<crow::sse_socket::ConnectionImpl< + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> + myConnection = std::make_shared<crow::sse_socket::ConnectionImpl< + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( + std::move(adaptor), openHandler, closeHandler); + myConnection->start(); + } +#endif + + template <typename Func> + self_t& onopen(Func f) + { + openHandler = f; + return *this; + } + + template <typename Func> + self_t& onclose(Func f) + { + closeHandler = f; + return *this; + } + + private: + std::function<void(crow::sse_socket::Connection&)> openHandler; + std::function<void(crow::sse_socket::Connection&)> closeHandler; +}; + +} // namespace crow diff --git a/http/routing/taggedrule.hpp b/http/routing/taggedrule.hpp new file mode 100644 index 0000000000..ef62ab08da --- /dev/null +++ b/http/routing/taggedrule.hpp @@ -0,0 +1,68 @@ +#pragma once +#include "baserule.hpp" +#include "dynamicrule.hpp" +#include "ruleparametertraits.hpp" + +#include <boost/beast/http/verb.hpp> + +#include <memory> +#include <string> +#include <vector> + +namespace crow +{ +template <typename... Args> +class TaggedRule : + public BaseRule, + public RuleParameterTraits<TaggedRule<Args...>> +{ + public: + using self_t = TaggedRule<Args...>; + + explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) {} + + void validate() override + { + if (!handler) + { + throw std::runtime_error("no handler for url " + rule); + } + } + + template <typename Func> + void operator()(Func&& f) + { + static_assert( + black_magic::CallHelper< + Func, black_magic::S<crow::Request, + std::shared_ptr<bmcweb::AsyncResp>&, + Args...>>::value, + "Handler type is mismatched with URL parameters"); + static_assert( + std::is_same< + void, + decltype(f(std::declval<crow::Request>(), + std::declval<std::shared_ptr<bmcweb::AsyncResp>&>(), + std::declval<Args>()...))>::value, + "Handler function with response argument should have void return type"); + + handler = std::forward<Func>(f); + } + + void handle(const Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::vector<std::string>& params) override + { + detail::routing_handler_call_helper::Call< + detail::routing_handler_call_helper::CallParams<decltype(handler)>, + 0, black_magic::S<Args...>, black_magic::S<>>()( + detail::routing_handler_call_helper::CallParams<decltype(handler)>{ + handler, params, req, asyncResp}); + } + + private: + std::function<void(const crow::Request&, + const std::shared_ptr<bmcweb::AsyncResp>&, Args...)> + handler; +}; +} // namespace crow diff --git a/http/routing/websocketrule.hpp b/http/routing/websocketrule.hpp new file mode 100644 index 0000000000..c8f706db51 --- /dev/null +++ b/http/routing/websocketrule.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "baserule.hpp" +#include "websocket.hpp" + +#include <boost/beast/http/verb.hpp> + +#include <memory> +#include <string> +#include <vector> + +namespace crow +{ +class WebSocketRule : public BaseRule +{ + using self_t = WebSocketRule; + + public: + explicit WebSocketRule(const std::string& ruleIn) : BaseRule(ruleIn) {} + + void validate() override {} + + void handle(const Request& /*req*/, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::vector<std::string>& /*params*/) override + { + asyncResp->res.result(boost::beast::http::status::not_found); + } + +#ifndef BMCWEB_ENABLE_SSL + void handleUpgrade(const Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, + boost::asio::ip::tcp::socket&& adaptor) override + { + BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; + std::shared_ptr< + crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>> + myConnection = std::make_shared< + crow::websocket::ConnectionImpl<boost::asio::ip::tcp::socket>>( + req, req.url(), std::move(adaptor), openHandler, messageHandler, + messageExHandler, closeHandler, errorHandler); + myConnection->start(); + } +#else + void handleUpgrade(const Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& /*asyncResp*/, + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>&& + adaptor) override + { + BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; + std::shared_ptr<crow::websocket::ConnectionImpl< + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>> + myConnection = std::make_shared<crow::websocket::ConnectionImpl< + boost::beast::ssl_stream<boost::asio::ip::tcp::socket>>>( + req, req.url(), std::move(adaptor), openHandler, messageHandler, + messageExHandler, closeHandler, errorHandler); + myConnection->start(); + } +#endif + + template <typename Func> + self_t& onopen(Func f) + { + openHandler = f; + return *this; + } + + template <typename Func> + self_t& onmessage(Func f) + { + messageHandler = f; + return *this; + } + + template <typename Func> + self_t& onmessageex(Func f) + { + messageExHandler = f; + return *this; + } + + template <typename Func> + self_t& onclose(Func f) + { + closeHandler = f; + return *this; + } + + template <typename Func> + self_t& onerror(Func f) + { + errorHandler = f; + return *this; + } + + protected: + std::function<void(crow::websocket::Connection&)> openHandler; + std::function<void(crow::websocket::Connection&, const std::string&, bool)> + messageHandler; + std::function<void(crow::websocket::Connection&, std::string_view, + crow::websocket::MessageType type, + std::function<void()>&& whenComplete)> + messageExHandler; + std::function<void(crow::websocket::Connection&, const std::string&)> + closeHandler; + std::function<void(crow::websocket::Connection&)> errorHandler; +}; +} // namespace crow |