Redfish: Query parameters: Only
Add the query parameter "only" for redfish.
The specification is based on DSP0266_1.8.0.
This commit is inspired by the commit that carries the same title, but
is largely unique, namely, in that it adds the core feature to be able
to recall handle with a new Response object, and make sure the result
gets to the connection. It does this by swapping the handlers and
implementing move semantics on the Response object. It definitely needs
broken up into a few smaller patches, but it does pass the below tests
without any apparent seg faults or ownership issues.
It implements a number of cleanups that deserve their own patches, and
will be split up accordingly, but for the moment, I think this is a good
start to getting filter and expand support in the future.
Tested:
Validator passes (on previous patchset)
~$ curl -i -k -H "X-Auth-Token: $token" -X GET "https://${bmc}/redfish/v1/Systems"
~$ curl -i -k -H "X-Auth-Token: $token" -X GET "https://${bmc}/redfish/v1/Systems?only"
Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: I123d8ab8bcd88a0b63ff131f6b98548951989755
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index 42f2e53..339294c 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -345,7 +345,6 @@
<< thisReq.methodString() << " " << thisReq.target()
<< " " << thisReq.ipAddress.to_string();
- res.setCompleteRequestHandler(nullptr);
res.isAliveHelper = [this]() -> bool { return isAlive(); };
thisReq.ioService = static_cast<decltype(thisReq.ioService)>(
diff --git a/redfish-core/include/query.hpp b/redfish-core/include/query.hpp
new file mode 100644
index 0000000..44f07ba
--- /dev/null
+++ b/redfish-core/include/query.hpp
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "utils/query_param.hpp"
+
+#include <bmcweb_config.h>
+
+namespace redfish
+{
+
+[[nodiscard]] inline bool setUpRedfishRoute(crow::App& app,
+ const crow::Request& req,
+ crow::Response& res)
+{
+ // If query parameters aren't enabled, do nothing.
+ if constexpr (!bmcwebInsecureEnableQueryParams)
+ {
+ return true;
+ }
+ std::optional<query_param::Query> queryOpt =
+ query_param::parseParameters(req.urlView.params(), res);
+ if (queryOpt == std::nullopt)
+ {
+ return false;
+ }
+
+ std::function<void(crow::Response&)> handler =
+ res.releaseCompleteRequestHandler();
+
+ res.setCompleteRequestHandler(
+ [&app, handler(std::move(handler)),
+ query{*queryOpt}](crow::Response& res) mutable {
+ processAllParams(app, query, res, handler);
+ });
+ return true;
+}
+} // namespace redfish
diff --git a/redfish-core/include/utils/query_param.hpp b/redfish-core/include/utils/query_param.hpp
new file mode 100644
index 0000000..d4ff81f
--- /dev/null
+++ b/redfish-core/include/utils/query_param.hpp
@@ -0,0 +1,127 @@
+#pragma once
+#include "app.hpp"
+#include "async_resp.hpp"
+#include "error_messages.hpp"
+#include "http_request.hpp"
+#include "routing.hpp"
+
+#include <string>
+#include <string_view>
+#include <vector>
+
+namespace redfish
+{
+namespace query_param
+{
+
+struct Query
+{
+ bool isOnly = false;
+};
+
+inline std::optional<Query>
+ parseParameters(const boost::urls::params_view& urlParams,
+ crow::Response& res)
+{
+ Query ret;
+ for (const boost::urls::params_view::value_type& it : urlParams)
+ {
+ std::string_view key(it.key.data(), it.key.size());
+ std::string_view value(it.value.data(), it.value.size());
+ if (key == "only")
+ {
+ if (!it.value.empty())
+ {
+ messages::queryParameterValueFormatError(res, value, key);
+ return std::nullopt;
+ }
+ ret.isOnly = true;
+ }
+ }
+ return ret;
+}
+
+inline bool processOnly(crow::App& app, crow::Response& res,
+ std::function<void(crow::Response&)>& completionHandler)
+{
+ BMCWEB_LOG_DEBUG << "Processing only query param";
+ auto itMembers = res.jsonValue.find("Members");
+ if (itMembers == res.jsonValue.end())
+ {
+ messages::queryNotSupportedOnResource(res);
+ completionHandler(res);
+ return false;
+ }
+ auto itMemBegin = itMembers->begin();
+ if (itMemBegin == itMembers->end() || itMembers->size() != 1)
+ {
+ BMCWEB_LOG_DEBUG << "Members contains " << itMembers->size()
+ << " element, returning full collection.";
+ completionHandler(res);
+ return false;
+ }
+
+ auto itUrl = itMemBegin->find("@odata.id");
+ if (itUrl == itMemBegin->end())
+ {
+ BMCWEB_LOG_DEBUG << "No found odata.id";
+ messages::internalError(res);
+ completionHandler(res);
+ return false;
+ }
+ const std::string* url = itUrl->get_ptr<const std::string*>();
+ if (url == nullptr)
+ {
+ BMCWEB_LOG_DEBUG << "@odata.id wasn't a string????";
+ messages::internalError(res);
+ completionHandler(res);
+ return false;
+ }
+ // TODO(Ed) copy request headers?
+ // newReq.session = req.session;
+ std::error_code ec;
+ crow::Request newReq({boost::beast::http::verb::get, *url, 11}, ec);
+ if (ec)
+ {
+ messages::internalError(res);
+ completionHandler(res);
+ return false;
+ }
+
+ auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
+ BMCWEB_LOG_DEBUG << "setting completion handler on " << &asyncResp->res;
+ asyncResp->res.setCompleteRequestHandler(std::move(completionHandler));
+ asyncResp->res.setIsAliveHelper(res.releaseIsAliveHelper());
+ app.handle(newReq, asyncResp);
+ return true;
+}
+
+void processAllParams(crow::App& app, Query query,
+ crow::Response& intermediateResponse,
+ std::function<void(crow::Response&)>& completionHandler)
+{
+ if (!completionHandler)
+ {
+ BMCWEB_LOG_DEBUG << "Function was invalid?";
+ return;
+ }
+
+ BMCWEB_LOG_DEBUG << "Processing query params";
+ // If the request failed, there's no reason to even try to run query
+ // params.
+ if (intermediateResponse.resultInt() < 200 ||
+ intermediateResponse.resultInt() >= 400)
+ {
+ completionHandler(intermediateResponse);
+ return;
+ }
+ if (query.isOnly)
+ {
+ processOnly(app, intermediateResponse, completionHandler);
+ return;
+ }
+ completionHandler(intermediateResponse);
+}
+
+} // namespace query_param
+} // namespace redfish
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index e2f32bb..696d6fd 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -15,6 +15,8 @@
*/
#pragma once
+#include <bmcweb_config.h>
+
#include <app.hpp>
#include <async_resp.hpp>
#include <http_request.hpp>
@@ -76,7 +78,7 @@
protocolFeatures["ExpandQuery"]["Links"] = false;
protocolFeatures["ExpandQuery"]["NoLinks"] = false;
protocolFeatures["FilterQuery"] = false;
- protocolFeatures["OnlyMemberQuery"] = false;
+ protocolFeatures["OnlyMemberQuery"] = bmcwebInsecureEnableQueryParams;
protocolFeatures["SelectQuery"] = false;
protocolFeatures["DeepOperations"]["DeepPOST"] = false;
protocolFeatures["DeepOperations"]["DeepPATCH"] = false;
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 429e992..83f9dad 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -19,6 +19,7 @@
#include "health.hpp"
#include "led.hpp"
#include "pcie.hpp"
+#include "query.hpp"
#include "redfish_util.hpp"
#include <app.hpp>
@@ -2652,8 +2653,12 @@
BMCWEB_ROUTE(app, "/redfish/v1/Systems/")
.privileges(redfish::privileges::getComputerSystemCollection)
.methods(boost::beast::http::verb::get)(
- [](const crow::Request& /*req*/,
- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+ [&app](const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
+ {
+ return;
+ }
asyncResp->res.jsonValue["@odata.type"] =
"#ComputerSystemCollection.ComputerSystemCollection";
asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/Systems";
diff --git a/redfish-core/lib/ut/service_root_test.cpp b/redfish-core/lib/ut/service_root_test.cpp
index 61b0d02..4c05751 100644
--- a/redfish-core/lib/ut/service_root_test.cpp
+++ b/redfish-core/lib/ut/service_root_test.cpp
@@ -73,7 +73,8 @@
EXPECT_FALSE(json["ProtocolFeaturesSupported"]["ExpandQuery"]["NoLinks"]);
EXPECT_EQ(json["ProtocolFeaturesSupported"]["ExpandQuery"].size(), 4);
EXPECT_FALSE(json["ProtocolFeaturesSupported"]["FilterQuery"]);
- EXPECT_FALSE(json["ProtocolFeaturesSupported"]["OnlyMemberQuery"]);
+ EXPECT_EQ(json["ProtocolFeaturesSupported"]["OnlyMemberQuery"],
+ bmcwebInsecureEnableQueryParams);
EXPECT_FALSE(json["ProtocolFeaturesSupported"]["SelectQuery"]);
EXPECT_FALSE(
json["ProtocolFeaturesSupported"]["DeepOperations"]["DeepPOST"]);