blob: 80a65128afae7eaa550cf970a36589df4570c9ef [file] [log] [blame]
#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"
#include "uri_to_entity.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"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
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;
}
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();
}
void handle(Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp);
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
bool allowSessionEmpty = false; // NOLINT
};
} // namespace crow
#endif // THIRD_PARTY_GBMCWEB_HTTP_ROUTING_H_