#pragma once #include "async_resp.hpp" #include "common.hpp" #include "dbus_utility.hpp" #include "error_messages.hpp" #include "http_request.hpp" #include "http_response.hpp" #include "logging.hpp" #include "privileges.hpp" #include "sessions.hpp" #include "utility.hpp" #include "verb.hpp" #include "websocket.hpp" #include #include #include #include #include #include #include #include #include #include #include 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 upgrade() { if (ruleToUpgrade) { return std::move(ruleToUpgrade); } return {}; } virtual void handle(const Request& /*req*/, const std::shared_ptr&, const RoutingParams&) = 0; virtual void handleUpgrade(const Request& /*req*/, Response& res, boost::asio::ip::tcp::socket&& /*adaptor*/) { res.result(boost::beast::http::status::not_found); res.end(); } #ifdef BMCWEB_ENABLE_SSL virtual void handleUpgrade( const Request& /*req*/, Response& res, boost::beast::ssl_stream&& /*adaptor*/) { res.result(boost::beast::http::status::not_found); res.end(); } #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(HttpVerb::Get)}; static_assert(std::numeric_limits::digits > methodNotAllowedIndex, "Not enough bits to store bitfield"); std::vector privilegesSet; std::string rule; std::string nameStr; std::unique_ptr ruleToUpgrade; friend class Router; template friend struct RuleParameterTraits; }; namespace detail { namespace routing_handler_call_helper { template struct CallPair { using type = T; static const int pos = Pos; }; template struct CallParams { H1& handler; const RoutingParams& params; const Request& req; const std::shared_ptr& asyncResp; }; template struct Call {}; template struct Call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back< CallPair>; Call, pushed>()(cparams); } }; template struct Call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back< CallPair>; Call, pushed>()(cparams); } }; template struct Call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back< CallPair>; Call, pushed>()(cparams); } }; template struct Call, black_magic::S> { void operator()(F cparams) { using pushed = typename black_magic::S::template push_back< CallPair>; Call, pushed>()(cparams); } }; template struct Call, black_magic::S> { void operator()(F cparams) { cparams.handler( cparams.req, cparams.asyncResp, cparams.params.template get(Args1::pos)...); } }; template struct Wrapped { template void set( Func f, typename std::enable_if< !std::is_same< typename std::tuple_element<0, std::tuple>::type, const Request&>::value, int>::type /*enable*/ = 0) { handler = [f = std::forward(f)]( const Request&, const std::shared_ptr& asyncResp, Args... args) { asyncResp->res.result(f(args...)); }; } template struct ReqHandlerWrapper { explicit ReqHandlerWrapper(Func fIn) : f(std::move(fIn)) {} void operator()(const Request& req, const std::shared_ptr& asyncResp, Args... args) { asyncResp->res.result(f(req, args...)); } Func f; }; template void set( Func f, typename std::enable_if< std::is_same< typename std::tuple_element<0, std::tuple>::type, const Request&>::value && !std::is_same>::type, const std::shared_ptr&>::value, int>::type /*enable*/ = 0) { handler = ReqHandlerWrapper(std::move(f)); /*handler = ( [f = std::move(f)] (const Request& req, Response& res, Args... args){ res.result(f(req, args...)); res.end(); });*/ } template void set( Func f, typename std::enable_if< std::is_same< typename std::tuple_element<0, std::tuple>::type, const Request&>::value && std::is_same>::type, const std::shared_ptr&>::value, int>::type /*enable*/ = 0) { handler = std::move(f); } template struct HandlerTypeHelper { using type = std::function&, Args...)>; using args_type = black_magic::S...>; }; template struct HandlerTypeHelper { using type = std::function&, Args...)>; using args_type = black_magic::S...>; }; template struct HandlerTypeHelper&, Args...> { using type = std::function&, Args...)>; using args_type = black_magic::S...>; }; typename HandlerTypeHelper::type handler; void operator()(const Request& req, const std::shared_ptr& asyncResp, const RoutingParams& params) { detail::routing_handler_call_helper::Call< detail::routing_handler_call_helper::CallParams, 0, 0, 0, 0, typename HandlerTypeHelper::args_type, black_magic::S<>>()( detail::routing_handler_call_helper::CallParams{ 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& asyncResp, const RoutingParams& /*params*/) override { asyncResp->res.result(boost::beast::http::status::not_found); } void handleUpgrade(const Request& req, Response& /*res*/, boost::asio::ip::tcp::socket&& adaptor) override { BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; std::shared_ptr< crow::websocket::ConnectionImpl> myConnection = std::make_shared< crow::websocket::ConnectionImpl>( req, std::move(adaptor), openHandler, messageHandler, closeHandler, errorHandler); myConnection->start(); } #ifdef BMCWEB_ENABLE_SSL void handleUpgrade(const Request& req, Response& /*res*/, boost::beast::ssl_stream&& adaptor) override { BMCWEB_LOG_DEBUG << "Websocket handles upgrade"; std::shared_ptr>> myConnection = std::make_shared>>( req, std::move(adaptor), openHandler, messageHandler, closeHandler, errorHandler); myConnection->start(); } #endif template self_t& onopen(Func f) { openHandler = f; return *this; } template self_t& onmessage(Func f) { messageHandler = f; return *this; } template self_t& onclose(Func f) { closeHandler = f; return *this; } template self_t& onerror(Func f) { errorHandler = f; return *this; } protected: std::function openHandler; std::function messageHandler; std::function closeHandler; std::function errorHandler; }; template struct RuleParameterTraits { using self_t = T; WebSocketRule& websocket() { self_t* self = static_cast(this); WebSocketRule* p = new WebSocketRule(self->rule); self->ruleToUpgrade.reset(p); return *p; } self_t& name(const std::string_view name) noexcept { self_t* self = static_cast(this); self->nameStr = name; return *self; } self_t& methods(boost::beast::http::verb method) { self_t* self = static_cast(this); std::optional verb = httpVerbFromBoost(method); if (verb) { self->methodsBitfield = 1U << static_cast(*verb); } return *self; } template self_t& methods(boost::beast::http::verb method, MethodArgs... argsMethod) { self_t* self = static_cast(this); methods(argsMethod...); std::optional verb = httpVerbFromBoost(method); if (verb) { self->methodsBitfield |= 1U << static_cast(*verb); } return *self; } self_t& notFound() { self_t* self = static_cast(this); self->methodsBitfield = 1U << notFoundIndex; return *self; } self_t& methodNotAllowed() { self_t* self = static_cast(this); self->methodsBitfield = 1U << methodNotAllowedIndex; return *self; } self_t& privileges( const std::initializer_list>& p) { self_t* self = static_cast(this); for (const std::initializer_list& privilege : p) { self->privilegesSet.emplace_back(privilege); } return *self; } template self_t& privileges(const std::array& p) { self_t* self = static_cast(this); for (const redfish::Privileges& privilege : p) { self->privilegesSet.emplace_back(privilege); } return *self; } }; class DynamicRule : public BaseRule, public RuleParameterTraits { 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& asyncResp, const RoutingParams& params) override { erasedHandler(req, asyncResp, params); } template void operator()(Func f) { using boost::callable_traits::args_t; constexpr size_t arity = std::tuple_size>::value; constexpr auto is = std::make_integer_sequence{}; erasedHandler = wrap(std::move(f), is); } // enable_if Arg1 == request && Arg2 == Response // enable_if Arg1 == request && Arg2 != response // enable_if Arg1 != request template std::function&, const RoutingParams&)> wrap(Func f, std::integer_sequence /*is*/) { using function_t = crow::utility::FunctionTraits; if (!black_magic::isParameterTagCompatible( black_magic::getParameterTag(rule.c_str()), black_magic::computeParameterTagFromArgsList< typename function_t::template arg...>::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...>(); ret.template set...>( std::move(f)); return ret; } template void operator()(std::string name, Func&& f) { nameStr = std::move(name); (*this).template operator()(std::forward(f)); } private: std::function&, const RoutingParams&)> erasedHandler; }; template class TaggedRule : public BaseRule, public RuleParameterTraits> { public: using self_t = TaggedRule; explicit TaggedRule(const std::string& ruleIn) : BaseRule(ruleIn) {} void validate() override { if (!handler) { throw std::runtime_error(nameStr + (!nameStr.empty() ? ": " : "") + "no handler for url " + rule); } } template typename std::enable_if< black_magic::CallHelper>::value, void>::type operator()(Func&& f) { static_assert( black_magic::CallHelper>::value || black_magic::CallHelper< Func, black_magic::S>::value, "Handler type is mismatched with URL parameters"); static_assert( !std::is_same()...))>::value, "Handler function cannot have void return type; valid return " "types: " "string, int, crow::response, nlohmann::json"); handler = [f = std::forward(f)]( const Request&, const std::shared_ptr& asyncResp, Args... args) { asyncResp->res.result(f(args...)); }; } template typename std::enable_if< !black_magic::CallHelper>::value && black_magic::CallHelper< Func, black_magic::S>::value, void>::type operator()(Func&& f) { static_assert( black_magic::CallHelper>::value || black_magic::CallHelper< Func, black_magic::S>::value, "Handler type is mismatched with URL parameters"); static_assert( !std::is_same(), std::declval()...))>::value, "Handler function cannot have void return type; valid return " "types: " "string, int, crow::response,nlohmann::json"); handler = [f = std::forward(f)]( const crow::Request& req, const std::shared_ptr& asyncResp, Args... args) { asyncResp->res.result(f(req, args...)); }; } template typename std::enable_if< !black_magic::CallHelper>::value && !black_magic::CallHelper< Func, black_magic::S>::value, void>::type operator()(Func&& f) { static_assert( black_magic::CallHelper>::value || black_magic::CallHelper< Func, black_magic::S>::value || black_magic::CallHelper< Func, black_magic::S&, Args...>>::value, "Handler type is mismatched with URL parameters"); static_assert( std::is_same< void, decltype(f(std::declval(), std::declval&>(), std::declval()...))>::value, "Handler function with response argument should have void " "return " "type"); handler = std::forward(f); } template void operator()(const std::string_view name, Func&& f) { nameStr = name; (*this).template operator()(std::forward(f)); } void handle(const Request& req, const std::shared_ptr& asyncResp, const RoutingParams& params) override { detail::routing_handler_call_helper::Call< detail::routing_handler_call_helper::CallParams, 0, 0, 0, 0, black_magic::S, black_magic::S<>>()( detail::routing_handler_call_helper::CallParams{ handler, params, req, asyncResp}); } private: std::function&, Args...)> handler; }; class Trie { public: struct Node { unsigned ruleIndex{}; std::array(ParamType::MAX)> paramChildrens{}; using ChildMap = boost::container::flat_map< std::string, unsigned, std::less<>, std::vector>>; 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) { for (size_t x : node->paramChildrens) { if (x == 0U) { continue; } Node* child = &nodes[x]; optimizeNode(child); } if (node->children.empty()) { return; } bool mergeWithChild = true; for (const Node::ChildMap::value_type& kv : node->children) { Node* child = &nodes[kv.second]; if (!child->isSimpleNode()) { mergeWithChild = false; break; } } if (mergeWithChild) { Node::ChildMap merged; for (const Node::ChildMap::value_type& kv : node->children) { Node* child = &nodes[kv.second]; for (const Node::ChildMap::value_type& childKv : child->children) { merged[kv.first + childKv.first] = childKv.second; } } node->children = std::move(merged); optimizeNode(node); } else { for (const Node::ChildMap::value_type& kv : node->children) { Node* child = &nodes[kv.second]; optimizeNode(child); } } } void optimize() { optimizeNode(head()); } public: void validate() { optimize(); } void findRouteIndexes(const std::string& reqUrl, std::vector& routeIndexes, const Node* node = nullptr, unsigned pos = 0) const { if (node == nullptr) { node = head(); } for (const Node::ChildMap::value_type& kv : node->children) { const std::string& fragment = kv.first; const Node* child = &nodes[kv.second]; if (pos >= reqUrl.size()) { if (child->ruleIndex != 0 && fragment != "/") { routeIndexes.push_back(child->ruleIndex); } findRouteIndexes(reqUrl, routeIndexes, child, static_cast(pos + fragment.size())); } else { if (reqUrl.compare(pos, fragment.size(), fragment) == 0) { findRouteIndexes( reqUrl, routeIndexes, child, static_cast(pos + fragment.size())); } } } } std::pair find(const std::string_view reqUrl, const Node* node = nullptr, size_t pos = 0, RoutingParams* params = nullptr) const { RoutingParams empty; if (params == nullptr) { params = ∅ } unsigned found{}; RoutingParams matchParams; if (node == nullptr) { node = head(); } if (pos == reqUrl.size()) { return {node->ruleIndex, *params}; } auto updateFound = [&found, &matchParams](std::pair& ret) { if (ret.first != 0U && (found == 0U || found > ret.first)) { found = ret.first; matchParams = std::move(ret.second); } }; if (node->paramChildrens[static_cast(ParamType::INT)] != 0U) { char c = reqUrl[pos]; if ((c >= '0' && c <= '9') || c == '+' || c == '-') { char* eptr = nullptr; errno = 0; long long int value = std::strtoll(reqUrl.data() + pos, &eptr, 10); if (errno != ERANGE && eptr != reqUrl.data() + pos) { params->intParams.push_back(value); std::pair ret = find(reqUrl, &nodes[node->paramChildrens[static_cast( ParamType::INT)]], static_cast(eptr - reqUrl.data()), params); updateFound(ret); params->intParams.pop_back(); } } } if (node->paramChildrens[static_cast(ParamType::UINT)] != 0U) { char c = reqUrl[pos]; if ((c >= '0' && c <= '9') || c == '+') { char* eptr = nullptr; errno = 0; unsigned long long int value = std::strtoull(reqUrl.data() + pos, &eptr, 10); if (errno != ERANGE && eptr != reqUrl.data() + pos) { params->uintParams.push_back(value); std::pair ret = find(reqUrl, &nodes[node->paramChildrens[static_cast( ParamType::UINT)]], static_cast(eptr - reqUrl.data()), params); updateFound(ret); params->uintParams.pop_back(); } } } if (node->paramChildrens[static_cast(ParamType::DOUBLE)] != 0U) { char c = reqUrl[pos]; if ((c >= '0' && c <= '9') || c == '+' || c == '-' || c == '.') { char* eptr = nullptr; errno = 0; double value = std::strtod(reqUrl.data() + pos, &eptr); if (errno != ERANGE && eptr != reqUrl.data() + pos) { params->doubleParams.push_back(value); std::pair ret = find(reqUrl, &nodes[node->paramChildrens[static_cast( ParamType::DOUBLE)]], static_cast(eptr - reqUrl.data()), params); updateFound(ret); params->doubleParams.pop_back(); } } } if (node->paramChildrens[static_cast(ParamType::STRING)] != 0U) { size_t epos = pos; for (; epos < reqUrl.size(); epos++) { if (reqUrl[epos] == '/') { break; } } if (epos != pos) { params->stringParams.emplace_back( reqUrl.substr(pos, epos - pos)); std::pair ret = find(reqUrl, &nodes[node->paramChildrens[static_cast( ParamType::STRING)]], epos, params); updateFound(ret); params->stringParams.pop_back(); } } if (node->paramChildrens[static_cast(ParamType::PATH)] != 0U) { size_t epos = reqUrl.size(); if (epos != pos) { params->stringParams.emplace_back( reqUrl.substr(pos, epos - pos)); std::pair ret = find(reqUrl, &nodes[node->paramChildrens[static_cast( ParamType::PATH)]], epos, params); updateFound(ret); params->stringParams.pop_back(); } } for (const Node::ChildMap::value_type& kv : node->children) { const std::string& fragment = kv.first; const Node* child = &nodes[kv.second]; if (reqUrl.compare(pos, fragment.size(), fragment) == 0) { std::pair ret = find(reqUrl, child, pos + fragment.size(), params); updateFound(ret); } } return {found, matchParams}; } void add(const std::string& url, unsigned ruleIndex) { size_t idx = 0; for (unsigned i = 0; i < url.size(); i++) { char c = url[i]; if (c == '<') { const static std::array, 7> paramTraits = {{ {ParamType::INT, ""}, {ParamType::UINT, ""}, {ParamType::DOUBLE, ""}, {ParamType::DOUBLE, ""}, {ParamType::STRING, ""}, {ParamType::STRING, ""}, {ParamType::PATH, ""}, }}; for (const std::pair& x : paramTraits) { if (url.compare(i, x.second.size(), x.second) == 0) { size_t index = static_cast(x.first); if (nodes[idx].paramChildrens[index] == 0U) { unsigned newNodeIdx = newNode(); nodes[idx].paramChildrens[index] = newNodeIdx; } idx = nodes[idx].paramChildrens[index]; i += static_cast(x.second.size()); break; } } i--; } else { std::string piece(&c, 1); if (nodes[idx].children.count(piece) == 0U) { unsigned newNodeIdx = newNode(); nodes[idx].children.emplace(piece, newNodeIdx); } idx = nodes[idx].children[piece]; } } if (nodes[idx].ruleIndex != 0U) { throw std::runtime_error("handler already exists for " + url); } nodes[idx].ruleIndex = ruleIndex; } private: void debugNodePrint(Node* n, size_t level) { for (size_t i = 0; i < static_cast(ParamType::MAX); i++) { if (n->paramChildrens[i] != 0U) { BMCWEB_LOG_DEBUG << std::string( 2U * level, ' ') /*<< "("<paramChildrens[i]<<") "*/; switch (static_cast(i)) { case ParamType::INT: BMCWEB_LOG_DEBUG << ""; break; case ParamType::UINT: BMCWEB_LOG_DEBUG << ""; break; case ParamType::DOUBLE: BMCWEB_LOG_DEBUG << ""; break; case ParamType::STRING: BMCWEB_LOG_DEBUG << ""; break; case ParamType::PATH: BMCWEB_LOG_DEBUG << ""; break; case ParamType::MAX: BMCWEB_LOG_DEBUG << ""; break; } debugNodePrint(&nodes[n->paramChildrens[i]], level + 1); } } for (const Node::ChildMap::value_type& kv : n->children) { BMCWEB_LOG_DEBUG << std::string(2U * level, ' ') /*<< "(" << kv.second << ") "*/ << kv.first; debugNodePrint(&nodes[kv.second], level + 1); } } 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(nodes.size() - 1); } std::vector nodes; }; class Router { public: Router() = default; DynamicRule& newRuleDynamic(const std::string& rule) { std::unique_ptr ruleObject = std::make_unique(rule); DynamicRule* ptr = ruleObject.get(); allRules.emplace_back(std::move(ruleObject)); return *ptr; } template typename black_magic::Arguments::type::template rebind& newRuleTagged(const std::string& rule) { using RuleT = typename black_magic::Arguments::type::template rebind< TaggedRule>; std::unique_ptr ruleObject = std::make_unique(rule); RuleT* ptr = ruleObject.get(); allRules.emplace_back(std::move(ruleObject)); return *ptr; } void internalAddRuleObject(const std::string& rule, BaseRule* ruleObject) { if (ruleObject == nullptr) { return; } for (size_t method = 0, methodBit = 1; method <= methodNotAllowedIndex; method++, methodBit <<= 1) { if ((ruleObject->methodsBitfield & methodBit) > 0U) { perMethods[method].rules.emplace_back(ruleObject); perMethods[method].trie.add( rule, static_cast( perMethods[method].rules.size() - 1U)); // directory case: // request to `/about' url matches `/about/' rule if (rule.size() > 2 && rule.back() == '/') { perMethods[method].trie.add( rule.substr(0, rule.size() - 1), static_cast(perMethods[method].rules.size() - 1)); } } } } void validate() { for (std::unique_ptr& rule : allRules) { if (rule) { std::unique_ptr upgraded = rule->upgrade(); if (upgraded) { rule = std::move(upgraded); } rule->validate(); internalAddRuleObject(rule->rule, rule.get()); } } for (PerMethod& perMethod : perMethods) { perMethod.trie.validate(); } } struct FindRoute { BaseRule* rule = nullptr; RoutingParams params; }; struct FindRouteResponse { std::string allowHeader; FindRoute route; }; FindRoute findRouteByIndex(std::string_view url, size_t index) const { FindRoute route; if (index >= perMethods.size()) { BMCWEB_LOG_CRITICAL << "Bad index???"; return route; } const PerMethod& perMethod = perMethods[index]; std::pair found = perMethod.trie.find(url); if (found.first >= perMethod.rules.size()) { throw std::runtime_error("Trie internal structure corrupted!"); } // Found a 404 route, switch that in if (found.first != 0U) { route.rule = perMethod.rules[found.first]; route.params = std::move(found.second); } return route; } FindRouteResponse findRoute(Request& req) const { FindRouteResponse findRoute; std::optional verb = httpVerbFromBoost(req.method()); if (!verb) { return findRoute; } size_t reqMethodIndex = static_cast(*verb); // Check to see if this url exists at any verb for (size_t perMethodIndex = 0; perMethodIndex <= maxVerbIndex; perMethodIndex++) { // Make sure it's safe to deference the array at that index static_assert(maxVerbIndex < std::tuple_size_v); FindRoute route = findRouteByIndex(req.url, perMethodIndex); if (route.rule == nullptr) { continue; } if (!findRoute.allowHeader.empty()) { findRoute.allowHeader += ", "; } HttpVerb thisVerb = static_cast(perMethodIndex); findRoute.allowHeader += httpVerbToString(thisVerb); if (perMethodIndex == reqMethodIndex) { findRoute.route = route; } } return findRoute; } template void handleUpgrade(const Request& req, Response& res, Adaptor&& adaptor) { std::optional verb = httpVerbFromBoost(req.method()); if (!verb || static_cast(*verb) >= perMethods.size()) { res.result(boost::beast::http::status::not_found); res.end(); return; } PerMethod& perMethod = perMethods[static_cast(*verb)]; Trie& trie = perMethod.trie; std::vector& rules = perMethod.rules; const std::pair& found = trie.find(req.url); unsigned ruleIndex = found.first; if (ruleIndex == 0U) { BMCWEB_LOG_DEBUG << "Cannot match rules " << req.url; res.result(boost::beast::http::status::not_found); res.end(); return; } if (ruleIndex >= rules.size()) { throw std::runtime_error("Trie internal structure corrupted!"); } if ((rules[ruleIndex]->getMethods() & (1U << static_cast(*verb))) == 0) { BMCWEB_LOG_DEBUG << "Rule found but method mismatch: " << req.url << " with " << req.methodString() << "(" << static_cast(*verb) << ") / " << rules[ruleIndex]->getMethods(); res.result(boost::beast::http::status::not_found); res.end(); return; } BMCWEB_LOG_DEBUG << "Matched rule (upgrade) '" << rules[ruleIndex]->rule << "' " << static_cast(*verb) << " / " << rules[ruleIndex]->getMethods(); // any uncaught exceptions become 500s try { rules[ruleIndex]->handleUpgrade(req, res, std::forward(adaptor)); } catch (const std::exception& e) { BMCWEB_LOG_ERROR << "An uncaught exception occurred: " << e.what(); res.result(boost::beast::http::status::internal_server_error); res.end(); return; } catch (...) { BMCWEB_LOG_ERROR << "An uncaught exception occurred. The type was unknown " "so no information was available."; res.result(boost::beast::http::status::internal_server_error); res.end(); return; } } void handle(Request& req, const std::shared_ptr& asyncResp) { std::optional verb = httpVerbFromBoost(req.method()); if (!verb || static_cast(*verb) >= perMethods.size()) { asyncResp->res.result(boost::beast::http::status::not_found); return; } FindRouteResponse foundRoute = findRoute(req); if (foundRoute.route.rule == nullptr) { // Couldn't find a normal route with any verb, try looking for a 404 // route if (foundRoute.allowHeader.empty()) { foundRoute.route = findRouteByIndex(req.url, notFoundIndex); } else { // See if we have a method not allowed (405) handler foundRoute.route = findRouteByIndex(req.url, methodNotAllowedIndex); } } // Fill in the allow header if it's valid if (!foundRoute.allowHeader.empty()) { asyncResp->res.addHeader(boost::beast::http::field::allow, foundRoute.allowHeader); } // If we couldn't find a real route or a 404 route, return a generic // response if (foundRoute.route.rule == nullptr) { if (foundRoute.allowHeader.empty()) { asyncResp->res.result(boost::beast::http::status::not_found); } else { asyncResp->res.result( boost::beast::http::status::method_not_allowed); } return; } BaseRule& rule = *foundRoute.route.rule; RoutingParams params = std::move(foundRoute.route.params); BMCWEB_LOG_DEBUG << "Matched rule '" << rule.rule << "' " << static_cast(*verb) << " / " << rule.getMethods(); if (req.session == nullptr) { rule.handle(req, asyncResp, params); return; } std::string username = req.session->username; crow::connections::systemBus->async_method_call( [req{std::move(req)}, asyncResp, &rule, params]( const boost::system::error_code ec, const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { if (ec) { BMCWEB_LOG_ERROR << "GetUserInfo failed..."; asyncResp->res.result( boost::beast::http::status::internal_server_error); return; } std::string userRole{}; const bool* remoteUser = nullptr; std::optional passwordExpired; for (const auto& userInfo : userInfoMap) { if (userInfo.first == "UserPrivilege") { const std::string* userRolePtr = std::get_if(&userInfo.second); if (userRolePtr == nullptr) { continue; } userRole = *userRolePtr; BMCWEB_LOG_DEBUG << "userName = " << req.session->username << " userRole = " << *userRolePtr; } else if (userInfo.first == "RemoteUser") { remoteUser = std::get_if(&userInfo.second); } else if (userInfo.first == "UserPasswordExpired") { const bool* passwordExpiredPtr = std::get_if(&userInfo.second); if (passwordExpiredPtr == nullptr) { continue; } passwordExpired = *passwordExpiredPtr; } } if (remoteUser == nullptr) { BMCWEB_LOG_ERROR << "RemoteUser property missing or wrong type"; asyncResp->res.result( boost::beast::http::status::internal_server_error); return; } if (passwordExpired == std::nullopt) { 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; } passwordExpired = false; } // Get the user's privileges from the role redfish::Privileges userPrivileges = redfish::getUserPrivileges(userRole); // 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 = *passwordExpired; // 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, crow::utility::urlFromPieces( "redfish", "v1", "AccountService", "Accounts", req.session->username)); } return; } req.userRole = userRole; rule.handle(req, asyncResp, params); }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", "GetUserInfo", username); } void debugPrint() { for (size_t i = 0; i < perMethods.size(); i++) { BMCWEB_LOG_DEBUG << boost::beast::http::to_string( static_cast(i)); perMethods[i].trie.debugPrint(); } } std::vector getRoutes(const std::string& parent) { std::vector ret; for (const PerMethod& pm : perMethods) { std::vector x; pm.trie.findRouteIndexes(parent, x); for (unsigned index : x) { ret.push_back(&pm.rules[index]->rule); } } return ret; } private: struct PerMethod { std::vector rules; Trie trie; // rule index 0 has special meaning; preallocate it to avoid // duplication. PerMethod() : rules(1) {} }; std::array perMethods; std::vector> allRules; }; } // namespace crow