xref: /openbmc/bmcweb/redfish-core/include/query.hpp (revision bd79bce8c3f1deb1fb2773868b9ece25233cf27b)
1f4c99e70SEd Tanous #pragma once
2f4c99e70SEd Tanous 
3e796c262SNan Zhou #include "bmcweb_config.h"
4e796c262SNan Zhou 
5e796c262SNan Zhou #include "app.hpp"
6e796c262SNan Zhou #include "async_resp.hpp"
7e796c262SNan Zhou #include "error_messages.hpp"
8e796c262SNan Zhou #include "http_request.hpp"
9e796c262SNan Zhou #include "http_response.hpp"
10e796c262SNan Zhou #include "logging.hpp"
11f4c99e70SEd Tanous #include "utils/query_param.hpp"
12f4c99e70SEd Tanous 
13e796c262SNan Zhou #include <boost/beast/http/verb.hpp>
14e796c262SNan Zhou #include <boost/url/params_view.hpp>
15e796c262SNan Zhou #include <boost/url/url_view.hpp>
16e796c262SNan Zhou 
17e796c262SNan Zhou #include <functional>
18e796c262SNan Zhou #include <memory>
19e796c262SNan Zhou #include <optional>
20e796c262SNan Zhou #include <string>
21e796c262SNan Zhou #include <string_view>
22e796c262SNan Zhou #include <type_traits>
23e796c262SNan Zhou #include <utility>
24e796c262SNan Zhou 
25e796c262SNan Zhou // IWYU pragma: no_forward_declare crow::App
26e796c262SNan Zhou // IWYU pragma: no_include <boost/url/impl/params_view.hpp>
27e796c262SNan Zhou // IWYU pragma: no_include <boost/url/impl/url_view.hpp>
28f4c99e70SEd Tanous 
293ccb3adbSEd Tanous #include "redfish_aggregator.hpp"
3005916cefSCarson Labrado 
31f4c99e70SEd Tanous namespace redfish
32f4c99e70SEd Tanous {
33102a4cdaSJonathan Doman inline void afterIfMatchRequest(
34102a4cdaSJonathan Doman     crow::App& app, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
35102a4cdaSJonathan Doman     const std::shared_ptr<crow::Request>& req, const std::string& ifMatchHeader,
362d6cb56bSEd Tanous     const crow::Response& resIn)
372d6cb56bSEd Tanous {
382d6cb56bSEd Tanous     std::string computedEtag = resIn.computeEtag();
3962598e31SEd Tanous     BMCWEB_LOG_DEBUG("User provided if-match etag {} computed etag {}",
4062598e31SEd Tanous                      ifMatchHeader, computedEtag);
412d6cb56bSEd Tanous     if (computedEtag != ifMatchHeader)
422d6cb56bSEd Tanous     {
432d6cb56bSEd Tanous         messages::preconditionFailed(asyncResp->res);
442d6cb56bSEd Tanous         return;
452d6cb56bSEd Tanous     }
462d6cb56bSEd Tanous     // Restart the request without if-match
47102a4cdaSJonathan Doman     req->clearHeader(boost::beast::http::field::if_match);
4862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Restarting request");
492d6cb56bSEd Tanous     app.handle(req, asyncResp);
502d6cb56bSEd Tanous }
512d6cb56bSEd Tanous 
522d6cb56bSEd Tanous inline bool handleIfMatch(crow::App& app, const crow::Request& req,
532d6cb56bSEd Tanous                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
542d6cb56bSEd Tanous {
552d6cb56bSEd Tanous     if (req.session == nullptr)
562d6cb56bSEd Tanous     {
572d6cb56bSEd Tanous         // If the user isn't authenticated, don't even attempt to parse match
582d6cb56bSEd Tanous         // parameters
592d6cb56bSEd Tanous         return true;
602d6cb56bSEd Tanous     }
612d6cb56bSEd Tanous 
622d6cb56bSEd Tanous     std::string ifMatch{
632d6cb56bSEd Tanous         req.getHeaderValue(boost::beast::http::field::if_match)};
642d6cb56bSEd Tanous     if (ifMatch.empty())
652d6cb56bSEd Tanous     {
662d6cb56bSEd Tanous         // No If-Match header.  Nothing to do
672d6cb56bSEd Tanous         return true;
682d6cb56bSEd Tanous     }
69a1cbc192SHieu Huynh     if (ifMatch == "*")
70a1cbc192SHieu Huynh     {
71a1cbc192SHieu Huynh         // Representing any resource
72a1cbc192SHieu Huynh         // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match
73a1cbc192SHieu Huynh         return true;
74a1cbc192SHieu Huynh     }
751873a04fSMyung Bae     if (req.method() != boost::beast::http::verb::patch &&
761873a04fSMyung Bae         req.method() != boost::beast::http::verb::post &&
771873a04fSMyung Bae         req.method() != boost::beast::http::verb::delete_)
782d6cb56bSEd Tanous     {
792d6cb56bSEd Tanous         messages::preconditionFailed(asyncResp->res);
802d6cb56bSEd Tanous         return false;
812d6cb56bSEd Tanous     }
822d6cb56bSEd Tanous     boost::system::error_code ec;
832d6cb56bSEd Tanous 
842d6cb56bSEd Tanous     // Try to GET the same resource
85102a4cdaSJonathan Doman     auto getReq = std::make_shared<crow::Request>(
86102a4cdaSJonathan Doman         crow::Request::Body{boost::beast::http::verb::get,
87102a4cdaSJonathan Doman                             req.url().encoded_path(), 11},
88102a4cdaSJonathan Doman         ec);
892d6cb56bSEd Tanous 
902d6cb56bSEd Tanous     if (ec)
912d6cb56bSEd Tanous     {
922d6cb56bSEd Tanous         messages::internalError(asyncResp->res);
932d6cb56bSEd Tanous         return false;
942d6cb56bSEd Tanous     }
952d6cb56bSEd Tanous 
962d6cb56bSEd Tanous     // New request has the same credentials as the old request
97102a4cdaSJonathan Doman     getReq->session = req.session;
982d6cb56bSEd Tanous 
992d6cb56bSEd Tanous     // Construct a new response object to fill in, and check the hash of before
1002d6cb56bSEd Tanous     // we modify the Resource.
1012d6cb56bSEd Tanous     std::shared_ptr<bmcweb::AsyncResp> getReqAsyncResp =
1022d6cb56bSEd Tanous         std::make_shared<bmcweb::AsyncResp>();
1032d6cb56bSEd Tanous 
104102a4cdaSJonathan Doman     // Ideally we would have a shared_ptr to the original Request which we could
105102a4cdaSJonathan Doman     // modify to remove the If-Match and restart it. But instead we have to make
106102a4cdaSJonathan Doman     // a full copy to restart it.
1072d6cb56bSEd Tanous     getReqAsyncResp->res.setCompleteRequestHandler(std::bind_front(
108102a4cdaSJonathan Doman         afterIfMatchRequest, std::ref(app), asyncResp,
109102a4cdaSJonathan Doman         std::make_shared<crow::Request>(req), std::move(ifMatch)));
1102d6cb56bSEd Tanous 
111102a4cdaSJonathan Doman     app.handle(getReq, getReqAsyncResp);
1122d6cb56bSEd Tanous     return false;
1132d6cb56bSEd Tanous }
114f4c99e70SEd Tanous 
115a6b9125fSNan Zhou // Sets up the Redfish Route and delegates some of the query parameter
116a6b9125fSNan Zhou // processing. |queryCapabilities| stores which query parameters will be
117a6b9125fSNan Zhou // handled by redfish-core/lib codes, then default query parameter handler won't
118a6b9125fSNan Zhou // process these parameters.
119a6b9125fSNan Zhou [[nodiscard]] inline bool setUpRedfishRouteWithDelegation(
1203ba00073SCarson Labrado     crow::App& app, const crow::Request& req,
1213ba00073SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
122a6b9125fSNan Zhou     query_param::Query& delegated,
123a6b9125fSNan Zhou     const query_param::QueryCapabilities& queryCapabilities)
124f4c99e70SEd Tanous {
12562598e31SEd Tanous     BMCWEB_LOG_DEBUG("setup redfish route");
126142ec9aeSEd Tanous 
127142ec9aeSEd Tanous     // Section 7.4 of the redfish spec "Redfish Services shall process the
128142ec9aeSEd Tanous     // [OData-Version header] in the following table as defined by the HTTP 1.1
129142ec9aeSEd Tanous     // specification..."
130142ec9aeSEd Tanous     // Required to pass redfish-protocol-validator REQ_HEADERS_ODATA_VERSION
131142ec9aeSEd Tanous     std::string_view odataHeader = req.getHeaderValue("OData-Version");
132142ec9aeSEd Tanous     if (!odataHeader.empty() && odataHeader != "4.0")
133142ec9aeSEd Tanous     {
1343ba00073SCarson Labrado         messages::preconditionFailed(asyncResp->res);
135142ec9aeSEd Tanous         return false;
136142ec9aeSEd Tanous     }
137142ec9aeSEd Tanous 
1383ba00073SCarson Labrado     asyncResp->res.addHeader("OData-Version", "4.0");
139c02a74f8SEd Tanous 
140f4c99e70SEd Tanous     std::optional<query_param::Query> queryOpt =
14139662a3bSEd Tanous         query_param::parseParameters(req.url().params(), asyncResp->res);
142e01d0c36SEd Tanous     if (!queryOpt)
143f4c99e70SEd Tanous     {
144f4c99e70SEd Tanous         return false;
145f4c99e70SEd Tanous     }
146f4c99e70SEd Tanous 
1472d6cb56bSEd Tanous     if (!handleIfMatch(app, req, asyncResp))
1482d6cb56bSEd Tanous     {
1492d6cb56bSEd Tanous         return false;
1502d6cb56bSEd Tanous     }
1512d6cb56bSEd Tanous 
15205916cefSCarson Labrado     bool needToCallHandlers = true;
15305916cefSCarson Labrado 
15425b54dbaSEd Tanous     if constexpr (BMCWEB_REDFISH_AGGREGATION)
15525b54dbaSEd Tanous     {
156*bd79bce8SPatrick Williams         needToCallHandlers =
157*bd79bce8SPatrick Williams             RedfishAggregator::beginAggregation(req, asyncResp) ==
158*bd79bce8SPatrick Williams             Result::LocalHandle;
15905916cefSCarson Labrado 
16025b54dbaSEd Tanous         // If the request should be forwarded to a satellite BMC then we don't
16125b54dbaSEd Tanous         // want to write anything to the asyncResp since it will get overwritten
16225b54dbaSEd Tanous         // later.
16325b54dbaSEd Tanous     }
16405916cefSCarson Labrado 
1657cf436c9SEd Tanous     // If this isn't a get, no need to do anything with parameters
1667cf436c9SEd Tanous     if (req.method() != boost::beast::http::verb::get)
1677cf436c9SEd Tanous     {
16805916cefSCarson Labrado         return needToCallHandlers;
1697cf436c9SEd Tanous     }
1707cf436c9SEd Tanous 
171a6b9125fSNan Zhou     delegated = query_param::delegate(queryCapabilities, *queryOpt);
172f4c99e70SEd Tanous     std::function<void(crow::Response&)> handler =
1733ba00073SCarson Labrado         asyncResp->res.releaseCompleteRequestHandler();
174827c4902SNan Zhou 
1753ba00073SCarson Labrado     asyncResp->res.setCompleteRequestHandler(
17632cdb4a7SWilly Tu         [&app, handler(std::move(handler)), query{std::move(*queryOpt)},
17732cdb4a7SWilly Tu          delegated{delegated}](crow::Response& resIn) mutable {
17832cdb4a7SWilly Tu             processAllParams(app, query, delegated, handler, resIn);
179f4c99e70SEd Tanous         });
180827c4902SNan Zhou 
18105916cefSCarson Labrado     return needToCallHandlers;
182f4c99e70SEd Tanous }
183a6b9125fSNan Zhou 
184a6b9125fSNan Zhou // Sets up the Redfish Route. All parameters are handled by the default handler.
1853ba00073SCarson Labrado [[nodiscard]] inline bool
1863ba00073SCarson Labrado     setUpRedfishRoute(crow::App& app, const crow::Request& req,
1873ba00073SCarson Labrado                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
188a6b9125fSNan Zhou {
189a6b9125fSNan Zhou     // This route |delegated| is never used
190a6b9125fSNan Zhou     query_param::Query delegated;
1913ba00073SCarson Labrado     return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated,
192a6b9125fSNan Zhou                                            query_param::QueryCapabilities{});
193a6b9125fSNan Zhou }
194f4c99e70SEd Tanous } // namespace redfish
195