xref: /openbmc/bmcweb/features/redfish/lib/redfish_v1.hpp (revision 04c39938e1ecb9d249a425039b5684da78db382a)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
34c25d66eSEd Tanous #pragma once
44c25d66eSEd Tanous 
53ccb3adbSEd Tanous #include "app.hpp"
6d7857201SEd Tanous #include "async_resp.hpp"
781d523a7SEd Tanous #include "error_messages.hpp"
83ccb3adbSEd Tanous #include "http_request.hpp"
93ccb3adbSEd Tanous #include "http_response.hpp"
10d7857201SEd Tanous #include "logging.hpp"
113ccb3adbSEd Tanous #include "query.hpp"
123ccb3adbSEd Tanous #include "registries/privilege_registry.hpp"
13d7857201SEd Tanous #include "str_utility.hpp"
14*04c39938SMyung Bae #include "utils/json_utils.hpp"
1581d523a7SEd Tanous 
16d7857201SEd Tanous #include <boost/beast/http/field.hpp>
17d7857201SEd Tanous #include <boost/beast/http/status.hpp>
18d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
19ef4c65b7SEd Tanous #include <boost/url/format.hpp>
20ef4c65b7SEd Tanous 
21d7857201SEd Tanous #include <array>
22d7857201SEd Tanous #include <filesystem>
23d7857201SEd Tanous #include <format>
24d7857201SEd Tanous #include <functional>
25d7857201SEd Tanous #include <memory>
263544d2a7SEd Tanous #include <ranges>
274c25d66eSEd Tanous #include <string>
28d7857201SEd Tanous #include <system_error>
29d7857201SEd Tanous #include <utility>
30d7857201SEd Tanous #include <vector>
314c25d66eSEd Tanous 
324c25d66eSEd Tanous namespace redfish
334c25d66eSEd Tanous {
344c25d66eSEd Tanous 
35d3355c5cSEd Tanous inline void redfishGet(App& app, const crow::Request& req,
36d3355c5cSEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
374c25d66eSEd Tanous {
383ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
391e925c84SEd Tanous     {
401e925c84SEd Tanous         return;
411e925c84SEd Tanous     }
421476687dSEd Tanous     asyncResp->res.jsonValue["v1"] = "/redfish/v1/";
43d3355c5cSEd Tanous }
44d3355c5cSEd Tanous 
458c623a96SEd Tanous inline void redfish404(App& app, const crow::Request& req,
468c623a96SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
478c623a96SEd Tanous                        const std::string& path)
488c623a96SEd Tanous {
498c623a96SEd Tanous     asyncResp->res.addHeader(boost::beast::http::field::allow, "");
508c623a96SEd Tanous 
518c623a96SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
528c623a96SEd Tanous     // 404
53686b7093SNan Zhou     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
548c623a96SEd Tanous     {
558c623a96SEd Tanous         return;
568c623a96SEd Tanous     }
578c623a96SEd Tanous 
5862598e31SEd Tanous     BMCWEB_LOG_WARNING("404 on path {}", path);
598c623a96SEd Tanous 
6039662a3bSEd Tanous     std::string name = req.url().segments().back();
618c623a96SEd Tanous     // Note, if we hit the wildcard route, we don't know the "type" the user was
628c623a96SEd Tanous     // actually requesting, but giving them a return with an empty string is
638c623a96SEd Tanous     // still better than nothing.
64079360aeSEd Tanous     messages::resourceNotFound(asyncResp->res, "", name);
658c623a96SEd Tanous }
668c623a96SEd Tanous 
6744c70412SEd Tanous inline void redfish405(App& app, const crow::Request& req,
6844c70412SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6944c70412SEd Tanous                        const std::string& path)
7044c70412SEd Tanous {
7144c70412SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
7244c70412SEd Tanous     // 405
7344c70412SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
7444c70412SEd Tanous     {
7544c70412SEd Tanous         return;
7644c70412SEd Tanous     }
7744c70412SEd Tanous 
7862598e31SEd Tanous     BMCWEB_LOG_WARNING("405 on path {}", path);
7944c70412SEd Tanous     asyncResp->res.result(boost::beast::http::status::method_not_allowed);
8044c70412SEd Tanous     if (req.method() == boost::beast::http::verb::delete_)
8144c70412SEd Tanous     {
8244c70412SEd Tanous         messages::resourceCannotBeDeleted(asyncResp->res);
8344c70412SEd Tanous     }
8444c70412SEd Tanous     else
8544c70412SEd Tanous     {
8644c70412SEd Tanous         messages::operationNotAllowed(asyncResp->res);
8744c70412SEd Tanous     }
8844c70412SEd Tanous }
8944c70412SEd Tanous 
90504af5a0SPatrick Williams inline void jsonSchemaIndexGet(
91504af5a0SPatrick Williams     App& app, const crow::Request& req,
9281d523a7SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
9381d523a7SEd Tanous {
9481d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
9581d523a7SEd Tanous     {
9681d523a7SEd Tanous         return;
9781d523a7SEd Tanous     }
9881d523a7SEd Tanous     nlohmann::json& json = asyncResp->res.jsonValue;
9981d523a7SEd Tanous     json["@odata.id"] = "/redfish/v1/JsonSchemas";
10081d523a7SEd Tanous     json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection";
10181d523a7SEd Tanous     json["Name"] = "JsonSchemaFile Collection";
10281d523a7SEd Tanous     json["Description"] = "Collection of JsonSchemaFiles";
103a529a6aaSEd Tanous 
104a529a6aaSEd Tanous     std::error_code ec;
105a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
106a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
107a529a6aaSEd Tanous     if (ec)
10881d523a7SEd Tanous     {
109a529a6aaSEd Tanous         messages::internalError(asyncResp->res);
110a529a6aaSEd Tanous         return;
111a529a6aaSEd Tanous     }
112*04c39938SMyung Bae 
113*04c39938SMyung Bae     std::vector<std::string> schemaNames;
114a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
115a529a6aaSEd Tanous     {
116a529a6aaSEd Tanous         std::string filename = file.filename();
117a529a6aaSEd Tanous         std::vector<std::string> split;
118a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
119a529a6aaSEd Tanous         if (split.empty())
120a529a6aaSEd Tanous         {
121a529a6aaSEd Tanous             continue;
122a529a6aaSEd Tanous         }
123*04c39938SMyung Bae         schemaNames.emplace_back(split[0]);
124*04c39938SMyung Bae     }
125*04c39938SMyung Bae     std::ranges::sort(schemaNames, AlphanumLess<std::string>());
126*04c39938SMyung Bae 
127*04c39938SMyung Bae     nlohmann::json::array_t members;
128*04c39938SMyung Bae     for (const std::string& schema : schemaNames)
129*04c39938SMyung Bae     {
13081d523a7SEd Tanous         nlohmann::json::object_t member;
131bd79bce8SPatrick Williams         member["@odata.id"] =
132*04c39938SMyung Bae             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
133ad539545SPatrick Williams         members.emplace_back(std::move(member));
13481d523a7SEd Tanous     }
135a529a6aaSEd Tanous     json["Members@odata.count"] = members.size();
13681d523a7SEd Tanous     json["Members"] = std::move(members);
13781d523a7SEd Tanous }
13881d523a7SEd Tanous 
13981d523a7SEd Tanous inline void jsonSchemaGet(App& app, const crow::Request& req,
14081d523a7SEd Tanous                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
14181d523a7SEd Tanous                           const std::string& schema)
14281d523a7SEd Tanous {
14381d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
14481d523a7SEd Tanous     {
14581d523a7SEd Tanous         return;
14681d523a7SEd Tanous     }
14781d523a7SEd Tanous 
148a529a6aaSEd Tanous     std::error_code ec;
149a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
150a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
151a529a6aaSEd Tanous     if (ec)
15281d523a7SEd Tanous     {
153d8a5d5d8SJiaqing Zhao         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
15481d523a7SEd Tanous         return;
15581d523a7SEd Tanous     }
156a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
157a529a6aaSEd Tanous     {
158a529a6aaSEd Tanous         std::string filename = file.filename();
159a529a6aaSEd Tanous         std::vector<std::string> split;
160a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
161a529a6aaSEd Tanous         if (split.empty())
162a529a6aaSEd Tanous         {
163a529a6aaSEd Tanous             continue;
164a529a6aaSEd Tanous         }
165a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("Checking {}", split[0]);
166a529a6aaSEd Tanous         if (split[0] != schema)
167a529a6aaSEd Tanous         {
168a529a6aaSEd Tanous             continue;
169a529a6aaSEd Tanous         }
17081d523a7SEd Tanous 
17181d523a7SEd Tanous         nlohmann::json& json = asyncResp->res.jsonValue;
172bd79bce8SPatrick Williams         json["@odata.id"] =
173bd79bce8SPatrick Williams             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
17481d523a7SEd Tanous         json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile";
17581d523a7SEd Tanous         json["Name"] = schema + " Schema File";
17681d523a7SEd Tanous         json["Description"] = schema + " Schema File Location";
17781d523a7SEd Tanous         json["Id"] = schema;
178a529a6aaSEd Tanous         std::string schemaName = std::format("#{}.{}", schema, schema);
17981d523a7SEd Tanous         json["Schema"] = std::move(schemaName);
18081d523a7SEd Tanous         constexpr std::array<std::string_view, 1> languages{"en"};
18181d523a7SEd Tanous         json["Languages"] = languages;
18281d523a7SEd Tanous         json["Languages@odata.count"] = languages.size();
18381d523a7SEd Tanous 
18481d523a7SEd Tanous         nlohmann::json::array_t locationArray;
18581d523a7SEd Tanous         nlohmann::json::object_t locationEntry;
18681d523a7SEd Tanous         locationEntry["Language"] = "en";
187a529a6aaSEd Tanous 
188a529a6aaSEd Tanous         locationEntry["PublicationUri"] = boost::urls::format(
189a529a6aaSEd Tanous             "http://redfish.dmtf.org/schemas/v1/{}", filename);
190ef4c65b7SEd Tanous         locationEntry["Uri"] = boost::urls::format(
191a529a6aaSEd Tanous             "/redfish/v1/JsonSchemas/{}/{}", schema, filename);
19281d523a7SEd Tanous 
19381d523a7SEd Tanous         locationArray.emplace_back(locationEntry);
19481d523a7SEd Tanous 
19581d523a7SEd Tanous         json["Location"] = std::move(locationArray);
19681d523a7SEd Tanous         json["Location@odata.count"] = 1;
197a529a6aaSEd Tanous         return;
198a529a6aaSEd Tanous     }
199a529a6aaSEd Tanous     messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
200a529a6aaSEd Tanous }
201a529a6aaSEd Tanous 
202504af5a0SPatrick Williams inline void jsonSchemaGetFile(
203504af5a0SPatrick Williams     const crow::Request& /*req*/,
204a529a6aaSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
205a529a6aaSEd Tanous     const std::string& schema, const std::string& schemaFile)
206a529a6aaSEd Tanous {
207a529a6aaSEd Tanous     // Sanity check the filename
208a529a6aaSEd Tanous     if (schemaFile.find_first_not_of(
209a529a6aaSEd Tanous             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") !=
210a529a6aaSEd Tanous         std::string::npos)
211a529a6aaSEd Tanous     {
212a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
213a529a6aaSEd Tanous         return;
214a529a6aaSEd Tanous     }
215a529a6aaSEd Tanous     // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json
216a529a6aaSEd Tanous     // Make sure the two paths match.
217a529a6aaSEd Tanous     if (!schemaFile.starts_with(schema))
218a529a6aaSEd Tanous     {
219a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
220a529a6aaSEd Tanous         return;
221a529a6aaSEd Tanous     }
222a529a6aaSEd Tanous     std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas");
223a529a6aaSEd Tanous     filepath /= schemaFile;
224a529a6aaSEd Tanous     if (filepath.is_relative())
225a529a6aaSEd Tanous     {
226a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
227a529a6aaSEd Tanous         return;
228a529a6aaSEd Tanous     }
229a529a6aaSEd Tanous 
230d51c61b4SMyung Bae     crow::OpenCode ec = asyncResp->res.openFile(filepath);
231d51c61b4SMyung Bae     if (ec == crow::OpenCode::FileDoesNotExist)
232d51c61b4SMyung Bae     {
233d51c61b4SMyung Bae         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
234d51c61b4SMyung Bae         return;
235d51c61b4SMyung Bae     }
236d51c61b4SMyung Bae     if (ec == crow::OpenCode::InternalError)
237a529a6aaSEd Tanous     {
238a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("failed to read file");
239d51c61b4SMyung Bae         messages::internalError(asyncResp->res);
240a529a6aaSEd Tanous         return;
241a529a6aaSEd Tanous     }
24281d523a7SEd Tanous }
24381d523a7SEd Tanous 
244f65fca6aSEd Tanous inline void requestRoutesRedfish(App& app)
245d3355c5cSEd Tanous {
246d3355c5cSEd Tanous     BMCWEB_ROUTE(app, "/redfish/")
247d3355c5cSEd Tanous         .methods(boost::beast::http::verb::get)(
248d3355c5cSEd Tanous             std::bind_front(redfishGet, std::ref(app)));
2498c623a96SEd Tanous 
250a529a6aaSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>")
251a529a6aaSEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
252a529a6aaSEd Tanous         .methods(boost::beast::http::verb::get)(jsonSchemaGetFile);
253a529a6aaSEd Tanous 
25481d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/")
2550ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFileCollection)
25681d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
25781d523a7SEd Tanous             std::bind_front(jsonSchemaGet, std::ref(app)));
25881d523a7SEd Tanous 
25981d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/")
2600ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
26181d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
26281d523a7SEd Tanous             std::bind_front(jsonSchemaIndexGet, std::ref(app)));
263e9dd1d31SEd Tanous 
264e9dd1d31SEd Tanous     // Note, this route must always be registered last
265e9dd1d31SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2660ea4b4e2SEd Tanous         .notFound()
2670ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2680ea4b4e2SEd Tanous             std::bind_front(redfish404, std::ref(app)));
26944c70412SEd Tanous 
27044c70412SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2710ea4b4e2SEd Tanous         .methodNotAllowed()
2720ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2730ea4b4e2SEd Tanous             std::bind_front(redfish405, std::ref(app)));
2744c25d66eSEd Tanous }
275f65fca6aSEd Tanous 
2764c25d66eSEd Tanous } // namespace redfish
277