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