| #ifndef THIRD_PARTY_GBMCWEB_HTTP_ROUTING_H_ | |
| #define THIRD_PARTY_GBMCWEB_HTTP_ROUTING_H_ | |
| #ifdef BMCWEB_ENABLE_GRPC | |
| #include "absl/log/log.h" | |
| #include "authorizer_enums.h" | |
| #include "bmcweb_authorizer_singleton.h" | |
| #endif | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| #include <functional> | |
| #include <initializer_list> | |
| #include <limits> | |
| #include <memory> | |
| #include <optional> | |
| #include <stdexcept> | |
| #include <string> | |
| #include <string_view> | |
| #include <tuple> | |
| #include <type_traits> | |
| #include <utility> | |
| #include <vector> | |
| #include "boost/beast/ssl/ssl_stream.hpp" // NOLINT | |
| #include "boost/container/flat_map.hpp" // NOLINT | |
| #include "common.hpp" | |
| #include "http_request.hpp" | |
| #include "http_response.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "verb.hpp" | |
| #include "websocket.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "privileges.hpp" | |
| // TODO(haoooamazing): Remove dbus_utils.h include once indirect include is | |
| // fixed for redfish-core/lib/ethernet.hpp | |
| #include "dbus_utils.hpp" // NOLINT(misc-include-cleaner) | |
| #include "managed_store.hpp" | |
| namespace crow { | |
| class BaseRule { | |
| public: | |
| explicit BaseRule(std::string_view 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 RoutingParams&) = 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; | |
| } | |
| // NOLINTNEXTLINE(readability-use_anyofallof) | |
| for (const redfish::Privileges& requiredPrivileges : privilegesSet) { | |
| if (userPrivileges.isSupersetOf(requiredPrivileges)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| size_t methodsBitfield{1 << static_cast<size_t>(HttpVerb::Get)}; // NOLINT | |
| static_assert(std::numeric_limits<decltype(methodsBitfield)>::digits > | |
| methodNotAllowedIndex, | |
| "Not enough bits to store bitfield"); | |
| std::vector<redfish::Privileges> privilegesSet; // NOLINT | |
| std::string rule; // NOLINT | |
| std::string nameStr; // NOLINT | |
| std::unique_ptr<BaseRule> ruleToUpgrade; // NOLINT | |
| 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 RoutingParams& params; | |
| const Request& req; | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp; // NOLINT | |
| }; | |
| template <typename F, int NInt, int NUint, int NDouble, int NString, | |
| typename S1, typename S2> | |
| struct Call {}; | |
| template <typename F, int NInt, int NUint, int NDouble, int NString, | |
| typename... Args1, typename... Args2> | |
| struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<int64_t, Args1...>, | |
| black_magic::S<Args2...>> { | |
| void operator()(F cparams) { | |
| using pushed = typename black_magic::S<Args2...>::template push_back< | |
| CallPair<int64_t, NInt>>; | |
| Call<F, NInt + 1, NUint, NDouble, NString, black_magic::S<Args1...>, | |
| pushed>()(cparams); | |
| } | |
| }; | |
| template <typename F, int NInt, int NUint, int NDouble, int NString, | |
| typename... Args1, typename... Args2> | |
| struct Call<F, NInt, NUint, NDouble, NString, | |
| black_magic::S<uint64_t, Args1...>, black_magic::S<Args2...>> { | |
| void operator()(F cparams) { | |
| using pushed = typename black_magic::S<Args2...>::template push_back< | |
| CallPair<uint64_t, NUint>>; | |
| Call<F, NInt, NUint + 1, NDouble, NString, black_magic::S<Args1...>, | |
| pushed>()(cparams); | |
| } | |
| }; | |
| template <typename F, int NInt, int NUint, int NDouble, int NString, | |
| typename... Args1, typename... Args2> | |
| struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<double, Args1...>, | |
| black_magic::S<Args2...>> { | |
| void operator()(F cparams) { | |
| using pushed = typename black_magic::S<Args2...>::template push_back< | |
| CallPair<double, NDouble>>; | |
| Call<F, NInt, NUint, NDouble + 1, NString, black_magic::S<Args1...>, | |
| pushed>()(cparams); | |
| } | |
| }; | |
| template <typename F, int NInt, int NUint, int NDouble, int NString, | |
| typename... Args1, typename... Args2> | |
| struct Call<F, NInt, NUint, NDouble, 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, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>, | |
| pushed>()(cparams); | |
| } | |
| }; | |
| template <typename F, int NInt, int NUint, int NDouble, int NString, | |
| typename... Args1> | |
| struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<>, | |
| black_magic::S<Args1...>> { | |
| void operator()(F cparams) { | |
| cparams.handler( | |
| cparams.req, cparams.asyncResp, | |
| cparams.params.template get<typename Args1::type>(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<typename black_magic::PromoteT<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<typename black_magic::PromoteT<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<typename black_magic::PromoteT<Args>...>; | |
| }; | |
| typename HandlerTypeHelper<ArgsWrapped...>::type handler; | |
| void operator()(const Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const RoutingParams& params) { | |
| detail::routing_handler_call_helper::Call< | |
| detail::routing_handler_call_helper::CallParams<decltype(handler)>, 0, | |
| 0, 0, 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 RoutingParams& /*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, 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, 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; // NOLINT | |
| std::function<void(crow::websocket::Connection&, const std::string&, bool)> | |
| messageHandler; // NOLINT | |
| std::function<void(crow::websocket::Connection&, std::string_view, | |
| crow::websocket::MessageType type, | |
| std::function<void()>&& whenComplete)> | |
| messageExHandler; // NOLINT | |
| std::function<void(crow::websocket::Connection&, const std::string&)> | |
| closeHandler; // NOLINT | |
| std::function<void(crow::websocket::Connection&)> errorHandler; // NOLINT | |
| }; | |
| 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); | |
| self->ruleToUpgrade.reset(p); | |
| return *p; | |
| } | |
| self_t& name(std::string_view name) noexcept { | |
| self_t* self = static_cast<self_t*>(this); | |
| self->nameStr = name; | |
| return *self; | |
| } | |
| 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(nameStr + (!nameStr.empty() ? ": " : "") + | |
| "no handler for url " + rule); | |
| } | |
| } | |
| void handle(const Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const RoutingParams& 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 RoutingParams&)> | |
| wrap(Func f, std::integer_sequence<unsigned, Indices...> /*is*/) { | |
| using function_t = crow::utility::FunctionTraits<Func>; | |
| if (!black_magic::isParameterTagCompatible( | |
| black_magic::getParameterTag(rule), | |
| black_magic::computeParameterTagFromArgsList< | |
| typename function_t::template arg<Indices>...>::value)) { | |
| throw std::runtime_error( | |
| "routeDynamic: Handler type is mismatched " | |
| "with URL parameters: " + | |
| rule); | |
| } | |
| 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; | |
| } | |
| template <typename Func> | |
| void operator()(std::string name, Func&& f) { | |
| nameStr = std::move(name); | |
| (*this).template operator()<Func>(std::forward(f)); | |
| } | |
| private: | |
| std::function<void(const Request&, const std::shared_ptr<bmcweb::AsyncResp>&, | |
| const RoutingParams&)> | |
| erasedHandler; // NOLINT | |
| }; | |
| template <typename... Args> | |
| class TaggedRule : public BaseRule, | |
| public RuleParameterTraits<TaggedRule<Args...>> { | |
| public: | |
| using self_t = TaggedRule<Args...>; | |
| explicit TaggedRule(std::string_view ruleIn) : BaseRule(ruleIn) {} | |
| void validate() override { | |
| if (!handler) { | |
| throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + | |
| "no handler for url " + rule); | |
| } | |
| } | |
| template <typename Func> | |
| typename std::enable_if< | |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value, void>::type | |
| operator()(Func&& f) { | |
| static_assert( | |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || | |
| black_magic::CallHelper< | |
| Func, black_magic::S<crow::Request, Args...>>::value, | |
| "Handler type is mismatched with URL parameters"); | |
| static_assert( | |
| !std::is_same<void, decltype(f(std::declval<Args>()...))>::value, | |
| "Handler function cannot have void return type; valid return " | |
| "types: " | |
| "string, int, crow::response, nlohmann::json"); | |
| handler = [f = std::forward<Func>(f)]( | |
| const Request&, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| Args... args) { asyncResp->res.result(f(args...)); }; | |
| } | |
| template <typename Func> | |
| typename std::enable_if< | |
| !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && | |
| black_magic::CallHelper< | |
| Func, black_magic::S<crow::Request, Args...>>::value, | |
| void>::type | |
| operator()(Func&& f) { | |
| static_assert( | |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || | |
| black_magic::CallHelper< | |
| Func, black_magic::S<crow::Request, Args...>>::value, | |
| "Handler type is mismatched with URL parameters"); | |
| static_assert( | |
| !std::is_same<void, decltype(f(std::declval<crow::Request>(), | |
| std::declval<Args>()...))>::value, | |
| "Handler function cannot have void return type; valid return " | |
| "types: " | |
| "string, int, crow::response,nlohmann::json"); | |
| handler = [f = std::forward<Func>(f)]( | |
| const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| Args... args) { asyncResp->res.result(f(req, args...)); }; | |
| } | |
| template <typename Func> | |
| typename std::enable_if< | |
| !black_magic::CallHelper<Func, black_magic::S<Args...>>::value && | |
| !black_magic::CallHelper< | |
| Func, black_magic::S<crow::Request, Args...>>::value, | |
| void>::type | |
| operator()(Func&& f) { | |
| static_assert( | |
| black_magic::CallHelper<Func, black_magic::S<Args...>>::value || | |
| black_magic::CallHelper< | |
| Func, black_magic::S<crow::Request, Args...>>::value || | |
| 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); | |
| } | |
| template <typename Func> | |
| void operator()(std::string_view name, Func&& f) { | |
| nameStr = name; | |
| (*this).template operator()<Func>(std::forward(f)); | |
| } | |
| template <typename Func> | |
| void replaceHandler(Func&& f) { | |
| this->operator()(f); | |
| } | |
| template <typename Func> | |
| void appendHandler(Func&& f) { | |
| this->operator()([f = std::forward<Func>(f), previousFunc = this->handler]( | |
| const crow::Request & req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| Args... args) { | |
| if (previousFunc) { | |
| previousFunc(req, asyncResp, args...); | |
| } | |
| std::function<void(crow::Response&)> previousCompleteFunc = | |
| asyncResp->res.releaseCompleteRequestHandler(); | |
| auto strand = asyncResp->strand_; | |
| asyncResp->res.setCompleteRequestHandler( | |
| [req, previousCompleteFunc(std::move(previousCompleteFunc)), args..., | |
| f, strand, delegatedExpandLevel(asyncResp->delegatedExpandLevel)]( | |
| crow::Response& resIn) mutable { | |
| std::shared_ptr<bmcweb::AsyncResp> newAsyncResp = | |
| std::make_shared<bmcweb::AsyncResp>(std::move(resIn), strand); | |
| // Pass delegatedExpandLevel so that hanlder in plugin can | |
| // handler efficient expand appending accordingly | |
| newAsyncResp->delegatedExpandLevel = delegatedExpandLevel; | |
| // previousCompleteFunc contains callback functions including | |
| // Redfish params and writing bytes to socket | |
| newAsyncResp->res.setCompleteRequestHandler( | |
| [previousCompleteFunc(std::move(previousCompleteFunc))]( | |
| crow::Response& resp) { | |
| if (previousCompleteFunc) { | |
| previousCompleteFunc(resp); | |
| } | |
| }); | |
| f(req, newAsyncResp, args...); | |
| }); | |
| }); | |
| } | |
| void handle(const Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const RoutingParams& params) override { | |
| detail::routing_handler_call_helper::Call< | |
| detail::routing_handler_call_helper::CallParams<decltype(handler)>, 0, | |
| 0, 0, 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; // NOLINT | |
| }; | |
| class Trie { | |
| public: | |
| struct Node { | |
| unsigned ruleIndex{}; // NOLINT | |
| std::array<size_t, static_cast<size_t>(ParamType::MAX)> | |
| paramChildrens{}; // NOLINT | |
| using ChildMap = boost::container::flat_map< | |
| std::string, unsigned, std::less<>, | |
| std::vector<std::pair<std::string, unsigned>>>; | |
| ChildMap children; | |
| bool isSimpleNode() const { | |
| return ruleIndex == 0 && | |
| std::all_of(std::begin(paramChildrens), std::end(paramChildrens), | |
| [](size_t x) { return x == 0U; }); | |
| } | |
| }; | |
| Trie() : nodes(1) {} | |
| private: | |
| void optimizeNode(Node* node); | |
| void optimize() { optimizeNode(head()); } | |
| public: | |
| void validate() { optimize(); } | |
| void findRouteIndexes(const std::string& reqUrl, | |
| std::vector<unsigned>& routeIndexes, | |
| const Node* node = nullptr, unsigned pos = 0) const; | |
| std::pair<unsigned, RoutingParams> find( | |
| std::string_view reqUrl, const Node* node = nullptr, size_t pos = 0, | |
| RoutingParams* params = nullptr) const; | |
| void add(const std::string& url, unsigned ruleIndex); | |
| private: | |
| void debugNodePrint(Node* n, size_t level); | |
| public: | |
| void debugPrint() { debugNodePrint(head(), 0U); } | |
| private: | |
| const Node* head() const { return &nodes.front(); } | |
| Node* head() { return &nodes.front(); } | |
| unsigned newNode() { | |
| nodes.resize(nodes.size() + 1); | |
| return static_cast<unsigned>(nodes.size() - 1); | |
| } | |
| std::vector<Node> nodes; // NOLINT | |
| }; | |
| class Router { | |
| public: | |
| explicit Router(bool allowSessionEmpty) | |
| : allowSessionEmpty(allowSessionEmpty) {} | |
| DynamicRule& newRuleDynamic(const std::string& rule) { | |
| std::unique_ptr<DynamicRule> ruleObject = | |
| std::make_unique<DynamicRule>(rule); | |
| DynamicRule* ptr = ruleObject.get(); | |
| allRules.emplace_back(std::move(ruleObject)); | |
| return *ptr; | |
| } | |
| // Catch all rule is the rule used by default if no other rule is found. | |
| // This is used to unit testing easier so that we don't have to register every | |
| // individual route. | |
| template <uint64_t N> | |
| typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& | |
| setCatchAllRule() { | |
| using RuleT = | |
| typename black_magic::Arguments<N>::type::template rebind<TaggedRule>; | |
| catch_all_rule_ = std::make_unique<RuleT>("*"); | |
| RuleT* ptr = static_cast<RuleT*>(catch_all_rule_.get()); | |
| return *ptr; | |
| } | |
| template <uint64_t N> | |
| typename black_magic::Arguments<N>::type::template rebind<TaggedRule>& | |
| newRuleTagged(std::string_view rule) { | |
| using RuleT = | |
| typename black_magic::Arguments<N>::type::template rebind<TaggedRule>; | |
| std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule); | |
| RuleT* ptr = ruleObject.get(); | |
| allRules.emplace_back(std::move(ruleObject)); | |
| return *ptr; | |
| } | |
| template <uint64_t Tag> | |
| void removeRuleTagged(std::string_view url, | |
| boost::beast::http::verb boostVerb) { | |
| auto iter = findRuleByUrlAndVerb<Tag>(url, boostVerb); | |
| if (iter == allRules.end()) { | |
| throwHandlerNotFound(url, boostVerb); | |
| } | |
| allRules.erase(iter); | |
| } | |
| void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject); | |
| void validate(); | |
| struct FindRoute { | |
| BaseRule* rule = nullptr; | |
| RoutingParams params; | |
| }; | |
| struct FindRouteResponse { | |
| std::string allowHeader; // NOLINT | |
| FindRoute route; | |
| }; | |
| FindRoute findRouteByIndex(std::string_view url, size_t index) const; | |
| FindRouteResponse findRoute(Request& req) const; | |
| 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 (!isUserPrivileged(req, asyncResp, rule, userInfoMap)) { | |
| // 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) { | |
| BMCWEB_LOG_DEBUG << "validatePrivilege: no session to validatePrivilege:"; | |
| if (allowSessionEmpty) { | |
| callback(req); | |
| } | |
| return; | |
| } | |
| if (managedStore::GetManagedObjectStore() | |
| ->GetDeprecatedThreadUnsafeSystemBus() != nullptr && | |
| req.session) { | |
| BMCWEB_LOG_DEBUG << "validatePrivilege: GetUserInfo"; | |
| const std::string username = req.session->username; | |
| // TODO: WHY ARE WE DOING THIS FOR EVERY REQUEST ?!?! OMG. this is | |
| // absurd! | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| [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); | |
| } else { | |
| // systemBus = null is most likely unit testing | |
| callback(req); | |
| } | |
| } | |
| template <typename Adaptor> | |
| void handleUpgrade(Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| Adaptor&& adaptor) { | |
| std::optional<HttpVerb> verb = httpVerbFromBoost(req.method()); | |
| if (!verb || static_cast<size_t>(*verb) >= perMethods.size()) { | |
| asyncResp->res.result(boost::beast::http::status::not_found); | |
| return; | |
| } | |
| PerMethod& perMethod = perMethods[static_cast<size_t>(*verb)]; | |
| Trie& trie = perMethod.trie; | |
| std::vector<BaseRule*>& rules = perMethod.rules; | |
| const std::pair<unsigned, RoutingParams>& found = // NOLINT | |
| trie.find(req.url().encoded_path()); | |
| unsigned ruleIndex = found.first; | |
| if (ruleIndex == 0U) { | |
| BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url().encoded_path(); | |
| asyncResp->res.result(boost::beast::http::status::not_found); | |
| return; | |
| } | |
| if (ruleIndex >= rules.size()) { | |
| throw std::runtime_error("Trie internal structure corrupted!"); | |
| } | |
| BaseRule& rule = *rules[ruleIndex]; | |
| size_t methods = rule.getMethods(); | |
| if ((methods & (1U << static_cast<size_t>(*verb))) == 0) { | |
| BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " | |
| << req.url().encoded_path() << " with " | |
| << req.methodString() << "(" | |
| << static_cast<uint32_t>(*verb) << ") / " << methods; | |
| asyncResp->res.result(boost::beast::http::status::not_found); | |
| return; | |
| } | |
| BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rule.rule << "' " | |
| << static_cast<uint32_t>(*verb) << " / " << methods; | |
| // TODO(ed) This should be able to use std::bind_front, but it doesn't | |
| // appear to work with the std::move on adaptor. | |
| validatePrivilege( | |
| req, asyncResp, rule, | |
| [&rule, asyncResp, | |
| adaptor(std::forward<Adaptor>(adaptor))](Request& thisReq) mutable { | |
| rule.handleUpgrade(thisReq, asyncResp, std::move(adaptor)); | |
| }); | |
| } | |
| template <uint64_t Tag> | |
| std::vector<std::unique_ptr<BaseRule>>::iterator findRuleByUrlAndVerb( | |
| std::string_view url, boost::beast::http::verb boostVerb) { | |
| std::optional<HttpVerb> verb = httpVerbFromBoost(boostVerb); | |
| if (!verb) { | |
| return allRules.end(); | |
| } | |
| for (auto iter = allRules.begin(); iter != allRules.end(); ++iter) { | |
| if ((*iter)->rule == url && | |
| ((*iter)->methodsBitfield & (1 << static_cast<size_t>(*verb))) > 0) { | |
| return iter; | |
| } | |
| } | |
| return allRules.end(); | |
| } | |
| template <uint64_t Tag> | |
| bool hasRuleTagged(std::string_view url, boost::beast::http::verb boostVerb) { | |
| return findRuleByUrlAndVerb<Tag>(url, boostVerb) != allRules.end(); | |
| } | |
| void handle(Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp); | |
| static bool isAccessAllowedFromInsecureTransport(const Request& req, | |
| std::string_view url_str); | |
| static bool needsAuthorization(const Request& req, std::string_view url_str); | |
| void debugPrint(); | |
| std::vector<const std::string*> getRoutes(const std::string& parent); | |
| static void throwHandlerNotFound(std::string_view url, | |
| boost::beast::http::verb boostVerb); | |
| template <uint64_t Tag, typename Func> | |
| void replaceHandler(std::string_view url, boost::beast::http::verb boostVerb, | |
| Func&& func) { | |
| auto iter = findRuleByUrlAndVerb<Tag>(url, boostVerb); | |
| if (iter == allRules.end()) { | |
| throwHandlerNotFound(url, boostVerb); | |
| } | |
| using RuleT = | |
| black_magic::Arguments<Tag>::type::template rebind<TaggedRule>; | |
| RuleT* rule = dynamic_cast<RuleT*>(iter->get()); | |
| rule->replaceHandler(func); | |
| } | |
| template <uint64_t Tag, typename Func> | |
| void appendHandler(std::string_view url, boost::beast::http::verb boostVerb, | |
| Func&& func) { | |
| auto iter = findRuleByUrlAndVerb<Tag>(url, boostVerb); | |
| if (iter == allRules.end()) { | |
| throwHandlerNotFound(url, boostVerb); | |
| } | |
| using RuleT = | |
| typename black_magic::Arguments<Tag>::type::template rebind<TaggedRule>; | |
| RuleT* rule = dynamic_cast<RuleT*>(iter->get()); | |
| rule->appendHandler(func); | |
| } | |
| private: | |
| static bool isUserPrivileged( | |
| Request& req, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| BaseRule& rule, const dbus::utility::DBusPropertiesMap& userInfoMap); | |
| struct PerMethod { | |
| std::vector<BaseRule*> rules; | |
| Trie trie; | |
| // rule index 0 has special meaning; preallocate it to avoid | |
| // duplication. | |
| PerMethod() : rules(1) {} | |
| }; | |
| std::array<PerMethod, methodNotAllowedIndex + 1> perMethods; // NOLINT | |
| std::vector<std::unique_ptr<BaseRule>> allRules; // NOLINT | |
| std::unique_ptr<BaseRule> catch_all_rule_; | |
| bool allowSessionEmpty = false; // NOLINT | |
| }; | |
| } // namespace crow | |
| #endif // THIRD_PARTY_GBMCWEB_HTTP_ROUTING_H_ |