1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "bmcweb_config.h" 6 7 #include "app.hpp" 8 #include "async_resp.hpp" 9 #include "error_messages.hpp" 10 #include "http_request.hpp" 11 #include "http_response.hpp" 12 #include "logging.hpp" 13 #include "utils/query_param.hpp" 14 15 #include <boost/beast/http/field.hpp> 16 #include <boost/beast/http/verb.hpp> 17 #include <boost/url/url_view.hpp> 18 19 #include <functional> 20 #include <memory> 21 #include <optional> 22 #include <string> 23 #include <string_view> 24 #include <type_traits> 25 #include <utility> 26 27 // IWYU pragma: no_forward_declare crow::App 28 29 #include "redfish_aggregator.hpp" 30 31 namespace redfish 32 { 33 inline void afterIfMatchRequest( 34 crow::App& app, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 35 const std::shared_ptr<crow::Request>& req, const std::string& ifMatchHeader, 36 const crow::Response& resIn) 37 { 38 std::string computedEtag = resIn.computeEtag(); 39 BMCWEB_LOG_DEBUG("User provided if-match etag {} computed etag {}", 40 ifMatchHeader, computedEtag); 41 if (computedEtag != ifMatchHeader) 42 { 43 messages::preconditionFailed(asyncResp->res); 44 return; 45 } 46 // Restart the request without if-match 47 req->clearHeader(boost::beast::http::field::if_match); 48 BMCWEB_LOG_DEBUG("Restarting request"); 49 app.handle(req, asyncResp); 50 } 51 52 inline bool handleIfMatch(crow::App& app, const crow::Request& req, 53 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 54 { 55 if (req.session == nullptr) 56 { 57 // If the user isn't authenticated, don't even attempt to parse match 58 // parameters 59 return true; 60 } 61 62 std::string ifMatch{ 63 req.getHeaderValue(boost::beast::http::field::if_match)}; 64 if (ifMatch.empty()) 65 { 66 // No If-Match header. Nothing to do 67 return true; 68 } 69 if (ifMatch == "*") 70 { 71 // Representing any resource 72 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Match 73 return true; 74 } 75 if (req.method() != boost::beast::http::verb::patch && 76 req.method() != boost::beast::http::verb::post && 77 req.method() != boost::beast::http::verb::delete_) 78 { 79 messages::preconditionFailed(asyncResp->res); 80 return false; 81 } 82 boost::system::error_code ec; 83 84 // Try to GET the same resource 85 auto getReq = std::make_shared<crow::Request>( 86 crow::Request::Body{boost::beast::http::verb::get, 87 req.url().encoded_path(), 11}, 88 ec); 89 90 if (ec) 91 { 92 messages::internalError(asyncResp->res); 93 return false; 94 } 95 96 // New request has the same credentials as the old request 97 getReq->session = req.session; 98 99 // Construct a new response object to fill in, and check the hash of before 100 // we modify the Resource. 101 std::shared_ptr<bmcweb::AsyncResp> getReqAsyncResp = 102 std::make_shared<bmcweb::AsyncResp>(); 103 104 // Ideally we would have a shared_ptr to the original Request which we could 105 // modify to remove the If-Match and restart it. But instead we have to make 106 // a full copy to restart it. 107 getReqAsyncResp->res.setCompleteRequestHandler(std::bind_front( 108 afterIfMatchRequest, std::ref(app), asyncResp, 109 std::make_shared<crow::Request>(req.copy()), std::move(ifMatch))); 110 111 app.handle(getReq, getReqAsyncResp); 112 return false; 113 } 114 115 // Sets up the Redfish Route and delegates some of the query parameter 116 // processing. |queryCapabilities| stores which query parameters will be 117 // handled by redfish-core/lib codes, then default query parameter handler won't 118 // process these parameters. 119 [[nodiscard]] inline bool setUpRedfishRouteWithDelegation( 120 crow::App& app, const crow::Request& req, 121 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 122 query_param::Query& delegated, 123 const query_param::QueryCapabilities& queryCapabilities) 124 { 125 BMCWEB_LOG_DEBUG("setup redfish route"); 126 127 // Section 7.4 of the redfish spec "Redfish Services shall process the 128 // [OData-Version header] in the following table as defined by the HTTP 1.1 129 // specification..." 130 // Required to pass redfish-protocol-validator REQ_HEADERS_ODATA_VERSION 131 std::string_view odataHeader = req.getHeaderValue("OData-Version"); 132 if (!odataHeader.empty() && odataHeader != "4.0") 133 { 134 messages::preconditionFailed(asyncResp->res); 135 return false; 136 } 137 138 asyncResp->res.addHeader("OData-Version", "4.0"); 139 140 std::optional<query_param::Query> queryOpt = 141 query_param::parseParameters(req.url().params(), asyncResp->res); 142 if (!queryOpt) 143 { 144 return false; 145 } 146 147 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH) 148 { 149 // Handle unauthorized expand query parameters for service root example 150 // /redfish/v1/?$expand=< > 151 if (req.session == nullptr && 152 queryOpt->expandType != query_param::ExpandType::None) 153 { 154 messages::resourceAtUriUnauthorized(asyncResp->res, req.url(), 155 "Invalid username or password"); 156 return false; 157 } 158 } 159 160 if (!handleIfMatch(app, req, asyncResp)) 161 { 162 return false; 163 } 164 165 bool needToCallHandlers = true; 166 167 if constexpr (BMCWEB_REDFISH_AGGREGATION) 168 { 169 needToCallHandlers = 170 RedfishAggregator::beginAggregation(req, asyncResp) == 171 Result::LocalHandle; 172 173 // If the request should be forwarded to a satellite BMC then we don't 174 // want to write anything to the asyncResp since it will get overwritten 175 // later. 176 } 177 178 // If this isn't a get, no need to do anything with parameters 179 if (req.method() != boost::beast::http::verb::get) 180 { 181 return needToCallHandlers; 182 } 183 184 // make a copy of the request because older request goes out of scope 185 // trying to access it after it goes out of scope will cause a crash 186 // Create a copy of the request using shared_ptr 187 188 // The lambda capture of newReq by value in the lambda is causing problems 189 // because the lambda needs to be copy-constructible, but crow::Request 190 // isn't copy-constructible. So we use a shared_ptr to the request to avoid 191 // copying the request. 192 // TODO: making a copy for every GET request is expensive, 193 // Cleanup is required here. 194 auto newReq = std::make_shared<crow::Request>(req.copy()); 195 196 delegated = query_param::delegate(queryCapabilities, *queryOpt); 197 std::function<void(crow::Response&)> handler = 198 asyncResp->res.releaseCompleteRequestHandler(); 199 200 asyncResp->res.setCompleteRequestHandler( 201 [&app, handler(std::move(handler)), query{std::move(*queryOpt)}, 202 delegated{delegated}, 203 newReq{std::move(newReq)}](crow::Response& resIn) mutable { 204 processAllParams(app, query, delegated, handler, resIn, *newReq); 205 }); 206 207 return needToCallHandlers; 208 } 209 210 // Sets up the Redfish Route. All parameters are handled by the default handler. 211 [[nodiscard]] inline bool setUpRedfishRoute( 212 crow::App& app, const crow::Request& req, 213 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 214 { 215 // This route |delegated| is never used 216 query_param::Query delegated; 217 return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated, 218 query_param::QueryCapabilities{}); 219 } 220 } // namespace redfish 221