xref: /openbmc/bmcweb/features/redfish/lib/redfish_v1.hpp (revision 3577e44683a5ade8ad02a6418984b56f4ca2bcac)
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"
10*3577e446SEd Tanous #include "human_sort.hpp"
11d7857201SEd Tanous #include "logging.hpp"
123ccb3adbSEd Tanous #include "query.hpp"
133ccb3adbSEd Tanous #include "registries/privilege_registry.hpp"
14d7857201SEd Tanous #include "str_utility.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 
21*3577e446SEd Tanous #include <algorithm>
22d7857201SEd Tanous #include <array>
23d7857201SEd Tanous #include <filesystem>
24d7857201SEd Tanous #include <format>
25d7857201SEd Tanous #include <functional>
26d7857201SEd Tanous #include <memory>
273544d2a7SEd Tanous #include <ranges>
284c25d66eSEd Tanous #include <string>
29d7857201SEd Tanous #include <system_error>
30d7857201SEd Tanous #include <utility>
31d7857201SEd Tanous #include <vector>
324c25d66eSEd Tanous 
334c25d66eSEd Tanous namespace redfish
344c25d66eSEd Tanous {
354c25d66eSEd Tanous 
36d3355c5cSEd Tanous inline void redfishGet(App& app, const crow::Request& req,
37d3355c5cSEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
384c25d66eSEd Tanous {
393ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
401e925c84SEd Tanous     {
411e925c84SEd Tanous         return;
421e925c84SEd Tanous     }
431476687dSEd Tanous     asyncResp->res.jsonValue["v1"] = "/redfish/v1/";
44d3355c5cSEd Tanous }
45d3355c5cSEd Tanous 
468c623a96SEd Tanous inline void redfish404(App& app, const crow::Request& req,
478c623a96SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
488c623a96SEd Tanous                        const std::string& path)
498c623a96SEd Tanous {
508c623a96SEd Tanous     asyncResp->res.addHeader(boost::beast::http::field::allow, "");
518c623a96SEd Tanous 
528c623a96SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
538c623a96SEd Tanous     // 404
54686b7093SNan Zhou     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
558c623a96SEd Tanous     {
568c623a96SEd Tanous         return;
578c623a96SEd Tanous     }
588c623a96SEd Tanous 
5962598e31SEd Tanous     BMCWEB_LOG_WARNING("404 on path {}", path);
608c623a96SEd Tanous 
6139662a3bSEd Tanous     std::string name = req.url().segments().back();
628c623a96SEd Tanous     // Note, if we hit the wildcard route, we don't know the "type" the user was
638c623a96SEd Tanous     // actually requesting, but giving them a return with an empty string is
648c623a96SEd Tanous     // still better than nothing.
65079360aeSEd Tanous     messages::resourceNotFound(asyncResp->res, "", name);
668c623a96SEd Tanous }
678c623a96SEd Tanous 
6844c70412SEd Tanous inline void redfish405(App& app, const crow::Request& req,
6944c70412SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
7044c70412SEd Tanous                        const std::string& path)
7144c70412SEd Tanous {
7244c70412SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
7344c70412SEd Tanous     // 405
7444c70412SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
7544c70412SEd Tanous     {
7644c70412SEd Tanous         return;
7744c70412SEd Tanous     }
7844c70412SEd Tanous 
7962598e31SEd Tanous     BMCWEB_LOG_WARNING("405 on path {}", path);
8044c70412SEd Tanous     asyncResp->res.result(boost::beast::http::status::method_not_allowed);
8144c70412SEd Tanous     if (req.method() == boost::beast::http::verb::delete_)
8244c70412SEd Tanous     {
8344c70412SEd Tanous         messages::resourceCannotBeDeleted(asyncResp->res);
8444c70412SEd Tanous     }
8544c70412SEd Tanous     else
8644c70412SEd Tanous     {
8744c70412SEd Tanous         messages::operationNotAllowed(asyncResp->res);
8844c70412SEd Tanous     }
8944c70412SEd Tanous }
9044c70412SEd Tanous 
91504af5a0SPatrick Williams inline void jsonSchemaIndexGet(
92504af5a0SPatrick Williams     App& app, const crow::Request& req,
9381d523a7SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
9481d523a7SEd Tanous {
9581d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
9681d523a7SEd Tanous     {
9781d523a7SEd Tanous         return;
9881d523a7SEd Tanous     }
9981d523a7SEd Tanous     nlohmann::json& json = asyncResp->res.jsonValue;
10081d523a7SEd Tanous     json["@odata.id"] = "/redfish/v1/JsonSchemas";
10181d523a7SEd Tanous     json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection";
10281d523a7SEd Tanous     json["Name"] = "JsonSchemaFile Collection";
10381d523a7SEd Tanous     json["Description"] = "Collection of JsonSchemaFiles";
104a529a6aaSEd Tanous 
105a529a6aaSEd Tanous     std::error_code ec;
106a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
107a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
108a529a6aaSEd Tanous     if (ec)
10981d523a7SEd Tanous     {
110a529a6aaSEd Tanous         messages::internalError(asyncResp->res);
111a529a6aaSEd Tanous         return;
112a529a6aaSEd Tanous     }
11304c39938SMyung Bae 
11404c39938SMyung Bae     std::vector<std::string> schemaNames;
115a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
116a529a6aaSEd Tanous     {
117a529a6aaSEd Tanous         std::string filename = file.filename();
118a529a6aaSEd Tanous         std::vector<std::string> split;
119a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
120a529a6aaSEd Tanous         if (split.empty())
121a529a6aaSEd Tanous         {
122a529a6aaSEd Tanous             continue;
123a529a6aaSEd Tanous         }
12404c39938SMyung Bae         schemaNames.emplace_back(split[0]);
12504c39938SMyung Bae     }
12604c39938SMyung Bae     std::ranges::sort(schemaNames, AlphanumLess<std::string>());
12704c39938SMyung Bae 
12804c39938SMyung Bae     nlohmann::json::array_t members;
12904c39938SMyung Bae     for (const std::string& schema : schemaNames)
13004c39938SMyung Bae     {
13181d523a7SEd Tanous         nlohmann::json::object_t member;
132bd79bce8SPatrick Williams         member["@odata.id"] =
13304c39938SMyung Bae             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
134ad539545SPatrick Williams         members.emplace_back(std::move(member));
13581d523a7SEd Tanous     }
136a529a6aaSEd Tanous     json["Members@odata.count"] = members.size();
13781d523a7SEd Tanous     json["Members"] = std::move(members);
13881d523a7SEd Tanous }
13981d523a7SEd Tanous 
14081d523a7SEd Tanous inline void jsonSchemaGet(App& app, const crow::Request& req,
14181d523a7SEd Tanous                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
14281d523a7SEd Tanous                           const std::string& schema)
14381d523a7SEd Tanous {
14481d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
14581d523a7SEd Tanous     {
14681d523a7SEd Tanous         return;
14781d523a7SEd Tanous     }
14881d523a7SEd Tanous 
149a529a6aaSEd Tanous     std::error_code ec;
150a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
151a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
152a529a6aaSEd Tanous     if (ec)
15381d523a7SEd Tanous     {
154d8a5d5d8SJiaqing Zhao         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
15581d523a7SEd Tanous         return;
15681d523a7SEd Tanous     }
157a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
158a529a6aaSEd Tanous     {
159a529a6aaSEd Tanous         std::string filename = file.filename();
160a529a6aaSEd Tanous         std::vector<std::string> split;
161a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
162a529a6aaSEd Tanous         if (split.empty())
163a529a6aaSEd Tanous         {
164a529a6aaSEd Tanous             continue;
165a529a6aaSEd Tanous         }
166a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("Checking {}", split[0]);
167a529a6aaSEd Tanous         if (split[0] != schema)
168a529a6aaSEd Tanous         {
169a529a6aaSEd Tanous             continue;
170a529a6aaSEd Tanous         }
17181d523a7SEd Tanous 
17281d523a7SEd Tanous         nlohmann::json& json = asyncResp->res.jsonValue;
173bd79bce8SPatrick Williams         json["@odata.id"] =
174bd79bce8SPatrick Williams             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
17581d523a7SEd Tanous         json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile";
17681d523a7SEd Tanous         json["Name"] = schema + " Schema File";
17781d523a7SEd Tanous         json["Description"] = schema + " Schema File Location";
17881d523a7SEd Tanous         json["Id"] = schema;
179a529a6aaSEd Tanous         std::string schemaName = std::format("#{}.{}", schema, schema);
18081d523a7SEd Tanous         json["Schema"] = std::move(schemaName);
18181d523a7SEd Tanous         constexpr std::array<std::string_view, 1> languages{"en"};
18281d523a7SEd Tanous         json["Languages"] = languages;
18381d523a7SEd Tanous         json["Languages@odata.count"] = languages.size();
18481d523a7SEd Tanous 
18581d523a7SEd Tanous         nlohmann::json::array_t locationArray;
18681d523a7SEd Tanous         nlohmann::json::object_t locationEntry;
18781d523a7SEd Tanous         locationEntry["Language"] = "en";
188a529a6aaSEd Tanous 
189a529a6aaSEd Tanous         locationEntry["PublicationUri"] = boost::urls::format(
190a529a6aaSEd Tanous             "http://redfish.dmtf.org/schemas/v1/{}", filename);
191ef4c65b7SEd Tanous         locationEntry["Uri"] = boost::urls::format(
192a529a6aaSEd Tanous             "/redfish/v1/JsonSchemas/{}/{}", schema, filename);
19381d523a7SEd Tanous 
19481d523a7SEd Tanous         locationArray.emplace_back(locationEntry);
19581d523a7SEd Tanous 
19681d523a7SEd Tanous         json["Location"] = std::move(locationArray);
19781d523a7SEd Tanous         json["Location@odata.count"] = 1;
198a529a6aaSEd Tanous         return;
199a529a6aaSEd Tanous     }
200a529a6aaSEd Tanous     messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
201a529a6aaSEd Tanous }
202a529a6aaSEd Tanous 
203504af5a0SPatrick Williams inline void jsonSchemaGetFile(
204504af5a0SPatrick Williams     const crow::Request& /*req*/,
205a529a6aaSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
206a529a6aaSEd Tanous     const std::string& schema, const std::string& schemaFile)
207a529a6aaSEd Tanous {
208a529a6aaSEd Tanous     // Sanity check the filename
209a529a6aaSEd Tanous     if (schemaFile.find_first_not_of(
210a529a6aaSEd Tanous             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") !=
211a529a6aaSEd Tanous         std::string::npos)
212a529a6aaSEd Tanous     {
213a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
214a529a6aaSEd Tanous         return;
215a529a6aaSEd Tanous     }
216a529a6aaSEd Tanous     // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json
217a529a6aaSEd Tanous     // Make sure the two paths match.
218a529a6aaSEd Tanous     if (!schemaFile.starts_with(schema))
219a529a6aaSEd Tanous     {
220a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
221a529a6aaSEd Tanous         return;
222a529a6aaSEd Tanous     }
223a529a6aaSEd Tanous     std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas");
224a529a6aaSEd Tanous     filepath /= schemaFile;
225a529a6aaSEd Tanous     if (filepath.is_relative())
226a529a6aaSEd Tanous     {
227a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
228a529a6aaSEd Tanous         return;
229a529a6aaSEd Tanous     }
230a529a6aaSEd Tanous 
231d51c61b4SMyung Bae     crow::OpenCode ec = asyncResp->res.openFile(filepath);
232d51c61b4SMyung Bae     if (ec == crow::OpenCode::FileDoesNotExist)
233d51c61b4SMyung Bae     {
234d51c61b4SMyung Bae         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
235d51c61b4SMyung Bae         return;
236d51c61b4SMyung Bae     }
237d51c61b4SMyung Bae     if (ec == crow::OpenCode::InternalError)
238a529a6aaSEd Tanous     {
239a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("failed to read file");
240d51c61b4SMyung Bae         messages::internalError(asyncResp->res);
241a529a6aaSEd Tanous         return;
242a529a6aaSEd Tanous     }
24381d523a7SEd Tanous }
24481d523a7SEd Tanous 
245f65fca6aSEd Tanous inline void requestRoutesRedfish(App& app)
246d3355c5cSEd Tanous {
247d3355c5cSEd Tanous     BMCWEB_ROUTE(app, "/redfish/")
248d3355c5cSEd Tanous         .methods(boost::beast::http::verb::get)(
249d3355c5cSEd Tanous             std::bind_front(redfishGet, std::ref(app)));
2508c623a96SEd Tanous 
251a529a6aaSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>")
252a529a6aaSEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
253a529a6aaSEd Tanous         .methods(boost::beast::http::verb::get)(jsonSchemaGetFile);
254a529a6aaSEd Tanous 
25581d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/")
2560ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFileCollection)
25781d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
25881d523a7SEd Tanous             std::bind_front(jsonSchemaGet, std::ref(app)));
25981d523a7SEd Tanous 
26081d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/")
2610ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
26281d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
26381d523a7SEd Tanous             std::bind_front(jsonSchemaIndexGet, std::ref(app)));
264e9dd1d31SEd Tanous 
265e9dd1d31SEd Tanous     // Note, this route must always be registered last
266e9dd1d31SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2670ea4b4e2SEd Tanous         .notFound()
2680ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2690ea4b4e2SEd Tanous             std::bind_front(redfish404, std::ref(app)));
27044c70412SEd Tanous 
27144c70412SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2720ea4b4e2SEd Tanous         .methodNotAllowed()
2730ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2740ea4b4e2SEd Tanous             std::bind_front(redfish405, std::ref(app)));
2754c25d66eSEd Tanous }
276f65fca6aSEd Tanous 
2774c25d66eSEd Tanous } // namespace redfish
278