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/verb.hpp>
16 #include <boost/url/params_view.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 {
afterIfMatchRequest(crow::App & app,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::shared_ptr<crow::Request> & req,const std::string & ifMatchHeader,const crow::Response & resIn)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
handleIfMatch(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)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), 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.
setUpRedfishRouteWithDelegation(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,query_param::Query & delegated,const query_param::QueryCapabilities & queryCapabilities)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 (!handleIfMatch(app, req, asyncResp))
148 {
149 return false;
150 }
151
152 bool needToCallHandlers = true;
153
154 if constexpr (BMCWEB_REDFISH_AGGREGATION)
155 {
156 needToCallHandlers =
157 RedfishAggregator::beginAggregation(req, asyncResp) ==
158 Result::LocalHandle;
159
160 // If the request should be forwarded to a satellite BMC then we don't
161 // want to write anything to the asyncResp since it will get overwritten
162 // later.
163 }
164
165 // If this isn't a get, no need to do anything with parameters
166 if (req.method() != boost::beast::http::verb::get)
167 {
168 return needToCallHandlers;
169 }
170
171 delegated = query_param::delegate(queryCapabilities, *queryOpt);
172 std::function<void(crow::Response&)> handler =
173 asyncResp->res.releaseCompleteRequestHandler();
174
175 asyncResp->res.setCompleteRequestHandler(
176 [&app, handler(std::move(handler)), query{std::move(*queryOpt)},
177 delegated{delegated}](crow::Response& resIn) mutable {
178 processAllParams(app, query, delegated, handler, resIn);
179 });
180
181 return needToCallHandlers;
182 }
183
184 // Sets up the Redfish Route. All parameters are handled by the default handler.
185 [[nodiscard]] inline bool
setUpRedfishRoute(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)186 setUpRedfishRoute(crow::App& app, const crow::Request& req,
187 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
188 {
189 // This route |delegated| is never used
190 query_param::Query delegated;
191 return setUpRedfishRouteWithDelegation(app, req, asyncResp, delegated,
192 query_param::QueryCapabilities{});
193 }
194 } // namespace redfish
195