xref: /openbmc/bmcweb/redfish-core/include/query.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
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  {
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.
setUpRedfishRoute(crow::App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)185  [[nodiscard]] inline bool setUpRedfishRoute(
186      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