xref: /openbmc/bmcweb/features/redfish/lib/redfish_v1.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
34c25d66eSEd Tanous #pragma once
44c25d66eSEd Tanous 
53ccb3adbSEd Tanous #include "app.hpp"
681d523a7SEd Tanous #include "error_messages.hpp"
73ccb3adbSEd Tanous #include "http_request.hpp"
83ccb3adbSEd Tanous #include "http_response.hpp"
93ccb3adbSEd Tanous #include "query.hpp"
103ccb3adbSEd Tanous #include "registries/privilege_registry.hpp"
1181d523a7SEd Tanous #include "utility.hpp"
1281d523a7SEd Tanous 
13ef4c65b7SEd Tanous #include <boost/url/format.hpp>
14ef4c65b7SEd Tanous 
153544d2a7SEd Tanous #include <ranges>
164c25d66eSEd Tanous #include <string>
174c25d66eSEd Tanous 
184c25d66eSEd Tanous namespace redfish
194c25d66eSEd Tanous {
204c25d66eSEd Tanous 
21d3355c5cSEd Tanous inline void redfishGet(App& app, const crow::Request& req,
22d3355c5cSEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
234c25d66eSEd Tanous {
243ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
251e925c84SEd Tanous     {
261e925c84SEd Tanous         return;
271e925c84SEd Tanous     }
281476687dSEd Tanous     asyncResp->res.jsonValue["v1"] = "/redfish/v1/";
29d3355c5cSEd Tanous }
30d3355c5cSEd Tanous 
318c623a96SEd Tanous inline void redfish404(App& app, const crow::Request& req,
328c623a96SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
338c623a96SEd Tanous                        const std::string& path)
348c623a96SEd Tanous {
358c623a96SEd Tanous     asyncResp->res.addHeader(boost::beast::http::field::allow, "");
368c623a96SEd Tanous 
378c623a96SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
388c623a96SEd Tanous     // 404
39686b7093SNan Zhou     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
408c623a96SEd Tanous     {
418c623a96SEd Tanous         return;
428c623a96SEd Tanous     }
438c623a96SEd Tanous 
4462598e31SEd Tanous     BMCWEB_LOG_WARNING("404 on path {}", path);
458c623a96SEd Tanous 
4639662a3bSEd Tanous     std::string name = req.url().segments().back();
478c623a96SEd Tanous     // Note, if we hit the wildcard route, we don't know the "type" the user was
488c623a96SEd Tanous     // actually requesting, but giving them a return with an empty string is
498c623a96SEd Tanous     // still better than nothing.
50079360aeSEd Tanous     messages::resourceNotFound(asyncResp->res, "", name);
518c623a96SEd Tanous }
528c623a96SEd Tanous 
5344c70412SEd Tanous inline void redfish405(App& app, const crow::Request& req,
5444c70412SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
5544c70412SEd Tanous                        const std::string& path)
5644c70412SEd Tanous {
5744c70412SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
5844c70412SEd Tanous     // 405
5944c70412SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
6044c70412SEd Tanous     {
6144c70412SEd Tanous         return;
6244c70412SEd Tanous     }
6344c70412SEd Tanous 
6462598e31SEd Tanous     BMCWEB_LOG_WARNING("405 on path {}", path);
6544c70412SEd Tanous     asyncResp->res.result(boost::beast::http::status::method_not_allowed);
6644c70412SEd Tanous     if (req.method() == boost::beast::http::verb::delete_)
6744c70412SEd Tanous     {
6844c70412SEd Tanous         messages::resourceCannotBeDeleted(asyncResp->res);
6944c70412SEd Tanous     }
7044c70412SEd Tanous     else
7144c70412SEd Tanous     {
7244c70412SEd Tanous         messages::operationNotAllowed(asyncResp->res);
7344c70412SEd Tanous     }
7444c70412SEd Tanous }
7544c70412SEd Tanous 
7681d523a7SEd Tanous inline void
7781d523a7SEd Tanous     jsonSchemaIndexGet(App& app, const crow::Request& req,
7881d523a7SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
7981d523a7SEd Tanous {
8081d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
8181d523a7SEd Tanous     {
8281d523a7SEd Tanous         return;
8381d523a7SEd Tanous     }
8481d523a7SEd Tanous     nlohmann::json& json = asyncResp->res.jsonValue;
8581d523a7SEd Tanous     json["@odata.id"] = "/redfish/v1/JsonSchemas";
8681d523a7SEd Tanous     json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection";
8781d523a7SEd Tanous     json["Name"] = "JsonSchemaFile Collection";
8881d523a7SEd Tanous     json["Description"] = "Collection of JsonSchemaFiles";
8981d523a7SEd Tanous     nlohmann::json::array_t members;
90a529a6aaSEd Tanous 
91a529a6aaSEd Tanous     std::error_code ec;
92a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
93a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
94a529a6aaSEd Tanous     if (ec)
9581d523a7SEd Tanous     {
96a529a6aaSEd Tanous         messages::internalError(asyncResp->res);
97a529a6aaSEd Tanous         return;
98a529a6aaSEd Tanous     }
99a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
100a529a6aaSEd Tanous     {
101a529a6aaSEd Tanous         std::string filename = file.filename();
102a529a6aaSEd Tanous         std::vector<std::string> split;
103a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
104a529a6aaSEd Tanous         if (split.empty())
105a529a6aaSEd Tanous         {
106a529a6aaSEd Tanous             continue;
107a529a6aaSEd Tanous         }
10881d523a7SEd Tanous         nlohmann::json::object_t member;
109bd79bce8SPatrick Williams         member["@odata.id"] =
110bd79bce8SPatrick Williams             boost::urls::format("/redfish/v1/JsonSchemas/{}", split[0]);
111ad539545SPatrick Williams         members.emplace_back(std::move(member));
11281d523a7SEd Tanous     }
113a529a6aaSEd Tanous 
114a529a6aaSEd Tanous     json["Members@odata.count"] = members.size();
11581d523a7SEd Tanous     json["Members"] = std::move(members);
11681d523a7SEd Tanous }
11781d523a7SEd Tanous 
11881d523a7SEd Tanous inline void jsonSchemaGet(App& app, const crow::Request& req,
11981d523a7SEd Tanous                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
12081d523a7SEd Tanous                           const std::string& schema)
12181d523a7SEd Tanous {
12281d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
12381d523a7SEd Tanous     {
12481d523a7SEd Tanous         return;
12581d523a7SEd Tanous     }
12681d523a7SEd Tanous 
127a529a6aaSEd Tanous     std::error_code ec;
128a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
129a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
130a529a6aaSEd Tanous     if (ec)
13181d523a7SEd Tanous     {
132d8a5d5d8SJiaqing Zhao         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
13381d523a7SEd Tanous         return;
13481d523a7SEd Tanous     }
135a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
136a529a6aaSEd Tanous     {
137a529a6aaSEd Tanous         std::string filename = file.filename();
138a529a6aaSEd Tanous         std::vector<std::string> split;
139a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
140a529a6aaSEd Tanous         if (split.empty())
141a529a6aaSEd Tanous         {
142a529a6aaSEd Tanous             continue;
143a529a6aaSEd Tanous         }
144a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("Checking {}", split[0]);
145a529a6aaSEd Tanous         if (split[0] != schema)
146a529a6aaSEd Tanous         {
147a529a6aaSEd Tanous             continue;
148a529a6aaSEd Tanous         }
14981d523a7SEd Tanous 
15081d523a7SEd Tanous         nlohmann::json& json = asyncResp->res.jsonValue;
151bd79bce8SPatrick Williams         json["@odata.id"] =
152bd79bce8SPatrick Williams             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
15381d523a7SEd Tanous         json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile";
15481d523a7SEd Tanous         json["Name"] = schema + " Schema File";
15581d523a7SEd Tanous         json["Description"] = schema + " Schema File Location";
15681d523a7SEd Tanous         json["Id"] = schema;
157a529a6aaSEd Tanous         std::string schemaName = std::format("#{}.{}", schema, schema);
15881d523a7SEd Tanous         json["Schema"] = std::move(schemaName);
15981d523a7SEd Tanous         constexpr std::array<std::string_view, 1> languages{"en"};
16081d523a7SEd Tanous         json["Languages"] = languages;
16181d523a7SEd Tanous         json["Languages@odata.count"] = languages.size();
16281d523a7SEd Tanous 
16381d523a7SEd Tanous         nlohmann::json::array_t locationArray;
16481d523a7SEd Tanous         nlohmann::json::object_t locationEntry;
16581d523a7SEd Tanous         locationEntry["Language"] = "en";
166a529a6aaSEd Tanous 
167a529a6aaSEd Tanous         locationEntry["PublicationUri"] = boost::urls::format(
168a529a6aaSEd Tanous             "http://redfish.dmtf.org/schemas/v1/{}", filename);
169ef4c65b7SEd Tanous         locationEntry["Uri"] = boost::urls::format(
170a529a6aaSEd Tanous             "/redfish/v1/JsonSchemas/{}/{}", schema, filename);
17181d523a7SEd Tanous 
17281d523a7SEd Tanous         locationArray.emplace_back(locationEntry);
17381d523a7SEd Tanous 
17481d523a7SEd Tanous         json["Location"] = std::move(locationArray);
17581d523a7SEd Tanous         json["Location@odata.count"] = 1;
176a529a6aaSEd Tanous         return;
177a529a6aaSEd Tanous     }
178a529a6aaSEd Tanous     messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
179a529a6aaSEd Tanous }
180a529a6aaSEd Tanous 
181a529a6aaSEd Tanous inline void
182a529a6aaSEd Tanous     jsonSchemaGetFile(const crow::Request& /*req*/,
183a529a6aaSEd Tanous                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
184a529a6aaSEd Tanous                       const std::string& schema, const std::string& schemaFile)
185a529a6aaSEd Tanous {
186a529a6aaSEd Tanous     // Sanity check the filename
187a529a6aaSEd Tanous     if (schemaFile.find_first_not_of(
188a529a6aaSEd Tanous             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") !=
189a529a6aaSEd Tanous         std::string::npos)
190a529a6aaSEd Tanous     {
191a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
192a529a6aaSEd Tanous         return;
193a529a6aaSEd Tanous     }
194a529a6aaSEd Tanous     // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json
195a529a6aaSEd Tanous     // Make sure the two paths match.
196a529a6aaSEd Tanous     if (!schemaFile.starts_with(schema))
197a529a6aaSEd Tanous     {
198a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
199a529a6aaSEd Tanous         return;
200a529a6aaSEd Tanous     }
201a529a6aaSEd Tanous     std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas");
202a529a6aaSEd Tanous     filepath /= schemaFile;
203a529a6aaSEd Tanous     if (filepath.is_relative())
204a529a6aaSEd Tanous     {
205a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
206a529a6aaSEd Tanous         return;
207a529a6aaSEd Tanous     }
208a529a6aaSEd Tanous 
209d51c61b4SMyung Bae     crow::OpenCode ec = asyncResp->res.openFile(filepath);
210d51c61b4SMyung Bae     if (ec == crow::OpenCode::FileDoesNotExist)
211d51c61b4SMyung Bae     {
212d51c61b4SMyung Bae         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
213d51c61b4SMyung Bae         return;
214d51c61b4SMyung Bae     }
215d51c61b4SMyung Bae     if (ec == crow::OpenCode::InternalError)
216a529a6aaSEd Tanous     {
217a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("failed to read file");
218d51c61b4SMyung Bae         messages::internalError(asyncResp->res);
219a529a6aaSEd Tanous         return;
220a529a6aaSEd Tanous     }
22181d523a7SEd Tanous }
22281d523a7SEd Tanous 
223f65fca6aSEd Tanous inline void requestRoutesRedfish(App& app)
224d3355c5cSEd Tanous {
225d3355c5cSEd Tanous     BMCWEB_ROUTE(app, "/redfish/")
226d3355c5cSEd Tanous         .methods(boost::beast::http::verb::get)(
227d3355c5cSEd Tanous             std::bind_front(redfishGet, std::ref(app)));
2288c623a96SEd Tanous 
229a529a6aaSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>")
230a529a6aaSEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
231a529a6aaSEd Tanous         .methods(boost::beast::http::verb::get)(jsonSchemaGetFile);
232a529a6aaSEd Tanous 
23381d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/")
2340ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFileCollection)
23581d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
23681d523a7SEd Tanous             std::bind_front(jsonSchemaGet, std::ref(app)));
23781d523a7SEd Tanous 
23881d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/")
2390ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
24081d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
24181d523a7SEd Tanous             std::bind_front(jsonSchemaIndexGet, std::ref(app)));
242e9dd1d31SEd Tanous 
243e9dd1d31SEd Tanous     // Note, this route must always be registered last
244e9dd1d31SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2450ea4b4e2SEd Tanous         .notFound()
2460ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2470ea4b4e2SEd Tanous             std::bind_front(redfish404, std::ref(app)));
24844c70412SEd Tanous 
24944c70412SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2500ea4b4e2SEd Tanous         .methodNotAllowed()
2510ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2520ea4b4e2SEd Tanous             std::bind_front(redfish405, std::ref(app)));
2534c25d66eSEd Tanous }
254f65fca6aSEd Tanous 
2554c25d66eSEd Tanous } // namespace redfish
256