140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0 240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3f4c99e70SEd Tanous #pragma once 4f4c99e70SEd Tanous 5e796c262SNan Zhou #include "bmcweb_config.h" 6e796c262SNan Zhou 7e796c262SNan Zhou #include "app.hpp" 8e796c262SNan Zhou #include "async_resp.hpp" 9e796c262SNan Zhou #include "error_messages.hpp" 10e796c262SNan Zhou #include "http_request.hpp" 11e796c262SNan Zhou #include "http_response.hpp" 12e796c262SNan Zhou #include "logging.hpp" 13f4c99e70SEd Tanous #include "utils/query_param.hpp" 14f4c99e70SEd Tanous 15d7857201SEd Tanous #include <boost/beast/http/field.hpp> 16e796c262SNan Zhou #include <boost/beast/http/verb.hpp> 17e796c262SNan Zhou #include <boost/url/url_view.hpp> 18e796c262SNan Zhou 19e796c262SNan Zhou #include <functional> 20e796c262SNan Zhou #include <memory> 21e796c262SNan Zhou #include <optional> 22e796c262SNan Zhou #include <string> 23e796c262SNan Zhou #include <string_view> 24e796c262SNan Zhou #include <type_traits> 25e796c262SNan Zhou #include <utility> 26e796c262SNan Zhou 27e796c262SNan Zhou // IWYU pragma: no_forward_declare crow::App 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 { 38*08fad5d9SCorey Ethington std::string currentEtag = resIn.getCurrentEtag(); 39*08fad5d9SCorey Ethington BMCWEB_LOG_DEBUG("User provided if-match etag {} current etag {}", 40*08fad5d9SCorey Ethington ifMatchHeader, currentEtag); 41*08fad5d9SCorey Ethington if (currentEtag != 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, 109608ad2ccSEd Tanous std::make_shared<crow::Request>(req.copy()), 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 14719fab295SChandramohan Harkude if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH) 14819fab295SChandramohan Harkude { 14919fab295SChandramohan Harkude // Handle unauthorized expand query parameters for service root example 15019fab295SChandramohan Harkude // /redfish/v1/?$expand=< > 15119fab295SChandramohan Harkude if (req.session == nullptr && 15219fab295SChandramohan Harkude queryOpt->expandType != query_param::ExpandType::None) 15319fab295SChandramohan Harkude { 15419fab295SChandramohan Harkude messages::resourceAtUriUnauthorized(asyncResp->res, req.url(), 15519fab295SChandramohan Harkude "Invalid username or password"); 15619fab295SChandramohan Harkude return false; 15719fab295SChandramohan Harkude } 15819fab295SChandramohan Harkude } 15919fab295SChandramohan Harkude 1602d6cb56bSEd Tanous if (!handleIfMatch(app, req, asyncResp)) 1612d6cb56bSEd Tanous { 1622d6cb56bSEd Tanous return false; 1632d6cb56bSEd Tanous } 1642d6cb56bSEd Tanous 16505916cefSCarson Labrado bool needToCallHandlers = true; 16605916cefSCarson Labrado 16725b54dbaSEd Tanous if constexpr (BMCWEB_REDFISH_AGGREGATION) 16825b54dbaSEd Tanous { 169bd79bce8SPatrick Williams needToCallHandlers = 17066620686SEd Tanous RedfishAggregator::getInstance().beginAggregation(req, asyncResp) == 171bd79bce8SPatrick Williams Result::LocalHandle; 17205916cefSCarson Labrado 17325b54dbaSEd Tanous // If the request should be forwarded to a satellite BMC then we don't 17425b54dbaSEd Tanous // want to write anything to the asyncResp since it will get overwritten 17525b54dbaSEd Tanous // later. 17625b54dbaSEd Tanous } 17705916cefSCarson Labrado 1787cf436c9SEd Tanous // If this isn't a get, no need to do anything with parameters 1797cf436c9SEd Tanous if (req.method() != boost::beast::http::verb::get) 1807cf436c9SEd Tanous { 18105916cefSCarson Labrado return needToCallHandlers; 1827cf436c9SEd Tanous } 1837cf436c9SEd Tanous 18419fab295SChandramohan Harkude // make a copy of the request because older request goes out of scope 18519fab295SChandramohan Harkude // trying to access it after it goes out of scope will cause a crash 18619fab295SChandramohan Harkude // Create a copy of the request using shared_ptr 18719fab295SChandramohan Harkude 18819fab295SChandramohan Harkude // The lambda capture of newReq by value in the lambda is causing problems 18919fab295SChandramohan Harkude // because the lambda needs to be copy-constructible, but crow::Request 19019fab295SChandramohan Harkude // isn't copy-constructible. So we use a shared_ptr to the request to avoid 19119fab295SChandramohan Harkude // copying the request. 19219fab295SChandramohan Harkude // TODO: making a copy for every GET request is expensive, 19319fab295SChandramohan Harkude // Cleanup is required here. 19419fab295SChandramohan Harkude auto newReq = std::make_shared<crow::Request>(req.copy()); 19519fab295SChandramohan Harkude 196a6b9125fSNan Zhou delegated = query_param::delegate(queryCapabilities, *queryOpt); 197f4c99e70SEd Tanous std::function<void(crow::Response&)> handler = 1983ba00073SCarson Labrado asyncResp->res.releaseCompleteRequestHandler(); 199827c4902SNan Zhou 2003ba00073SCarson Labrado asyncResp->res.setCompleteRequestHandler( 20132cdb4a7SWilly Tu [&app, handler(std::move(handler)), query{std::move(*queryOpt)}, 20219fab295SChandramohan Harkude delegated{delegated}, 20319fab295SChandramohan Harkude newReq{std::move(newReq)}](crow::Response& resIn) mutable { 20419fab295SChandramohan Harkude processAllParams(app, query, delegated, handler, resIn, *newReq); 205f4c99e70SEd Tanous }); 206827c4902SNan Zhou 20705916cefSCarson Labrado return needToCallHandlers; 208f4c99e70SEd Tanous } 209a6b9125fSNan Zhou 210a6b9125fSNan Zhou // Sets up the Redfish Route. All parameters are handled by the default handler. 211504af5a0SPatrick Williams [[nodiscard]] inline bool setUpRedfishRoute( 212504af5a0SPatrick Williams crow::App& app, const crow::Request& req, 2133ba00073SCarson Labrado const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 214a6b9125fSNan Zhou { 215a6b9125fSNan Zhou // This route |delegated| is never used 216a6b9125fSNan Zhou query_param::Query delegated; 2173ba00073SCarson Labrado return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated, 218a6b9125fSNan Zhou query_param::QueryCapabilities{}); 219a6b9125fSNan Zhou } 220f4c99e70SEd Tanous } // namespace redfish 221