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