xref: /openbmc/bmcweb/redfish-core/lib/redfish_v1.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
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"
1481d523a7SEd Tanous 
15d7857201SEd Tanous #include <boost/beast/http/field.hpp>
16d7857201SEd Tanous #include <boost/beast/http/status.hpp>
17d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
18ef4c65b7SEd Tanous #include <boost/url/format.hpp>
19ef4c65b7SEd Tanous 
20d7857201SEd Tanous #include <array>
21d7857201SEd Tanous #include <filesystem>
22d7857201SEd Tanous #include <format>
23d7857201SEd Tanous #include <functional>
24d7857201SEd Tanous #include <memory>
253544d2a7SEd Tanous #include <ranges>
264c25d66eSEd Tanous #include <string>
27d7857201SEd Tanous #include <system_error>
28d7857201SEd Tanous #include <utility>
29d7857201SEd Tanous #include <vector>
304c25d66eSEd Tanous 
314c25d66eSEd Tanous namespace redfish
324c25d66eSEd Tanous {
334c25d66eSEd Tanous 
redfishGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)34d3355c5cSEd Tanous inline void redfishGet(App& app, const crow::Request& req,
35d3355c5cSEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
364c25d66eSEd Tanous {
373ba00073SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
381e925c84SEd Tanous     {
391e925c84SEd Tanous         return;
401e925c84SEd Tanous     }
411476687dSEd Tanous     asyncResp->res.jsonValue["v1"] = "/redfish/v1/";
42d3355c5cSEd Tanous }
43d3355c5cSEd Tanous 
redfish404(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & path)448c623a96SEd Tanous inline void redfish404(App& app, const crow::Request& req,
458c623a96SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
468c623a96SEd Tanous                        const std::string& path)
478c623a96SEd Tanous {
488c623a96SEd Tanous     asyncResp->res.addHeader(boost::beast::http::field::allow, "");
498c623a96SEd Tanous 
508c623a96SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
518c623a96SEd Tanous     // 404
52686b7093SNan Zhou     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
538c623a96SEd Tanous     {
548c623a96SEd Tanous         return;
558c623a96SEd Tanous     }
568c623a96SEd Tanous 
5762598e31SEd Tanous     BMCWEB_LOG_WARNING("404 on path {}", path);
588c623a96SEd Tanous 
5939662a3bSEd Tanous     std::string name = req.url().segments().back();
608c623a96SEd Tanous     // Note, if we hit the wildcard route, we don't know the "type" the user was
618c623a96SEd Tanous     // actually requesting, but giving them a return with an empty string is
628c623a96SEd Tanous     // still better than nothing.
63079360aeSEd Tanous     messages::resourceNotFound(asyncResp->res, "", name);
648c623a96SEd Tanous }
658c623a96SEd Tanous 
redfish405(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & path)6644c70412SEd Tanous inline void redfish405(App& app, const crow::Request& req,
6744c70412SEd Tanous                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
6844c70412SEd Tanous                        const std::string& path)
6944c70412SEd Tanous {
7044c70412SEd Tanous     // If we fall to this route, we didn't have a more specific route, so return
7144c70412SEd Tanous     // 405
7244c70412SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
7344c70412SEd Tanous     {
7444c70412SEd Tanous         return;
7544c70412SEd Tanous     }
7644c70412SEd Tanous 
7762598e31SEd Tanous     BMCWEB_LOG_WARNING("405 on path {}", path);
7844c70412SEd Tanous     asyncResp->res.result(boost::beast::http::status::method_not_allowed);
7944c70412SEd Tanous     if (req.method() == boost::beast::http::verb::delete_)
8044c70412SEd Tanous     {
8144c70412SEd Tanous         messages::resourceCannotBeDeleted(asyncResp->res);
8244c70412SEd Tanous     }
8344c70412SEd Tanous     else
8444c70412SEd Tanous     {
8544c70412SEd Tanous         messages::operationNotAllowed(asyncResp->res);
8644c70412SEd Tanous     }
8744c70412SEd Tanous }
8844c70412SEd Tanous 
jsonSchemaIndexGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)89*504af5a0SPatrick Williams inline void jsonSchemaIndexGet(
90*504af5a0SPatrick Williams     App& app, const crow::Request& req,
9181d523a7SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
9281d523a7SEd Tanous {
9381d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
9481d523a7SEd Tanous     {
9581d523a7SEd Tanous         return;
9681d523a7SEd Tanous     }
9781d523a7SEd Tanous     nlohmann::json& json = asyncResp->res.jsonValue;
9881d523a7SEd Tanous     json["@odata.id"] = "/redfish/v1/JsonSchemas";
9981d523a7SEd Tanous     json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection";
10081d523a7SEd Tanous     json["Name"] = "JsonSchemaFile Collection";
10181d523a7SEd Tanous     json["Description"] = "Collection of JsonSchemaFiles";
10281d523a7SEd Tanous     nlohmann::json::array_t members;
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     }
112a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
113a529a6aaSEd Tanous     {
114a529a6aaSEd Tanous         std::string filename = file.filename();
115a529a6aaSEd Tanous         std::vector<std::string> split;
116a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
117a529a6aaSEd Tanous         if (split.empty())
118a529a6aaSEd Tanous         {
119a529a6aaSEd Tanous             continue;
120a529a6aaSEd Tanous         }
12181d523a7SEd Tanous         nlohmann::json::object_t member;
122bd79bce8SPatrick Williams         member["@odata.id"] =
123bd79bce8SPatrick Williams             boost::urls::format("/redfish/v1/JsonSchemas/{}", split[0]);
124ad539545SPatrick Williams         members.emplace_back(std::move(member));
12581d523a7SEd Tanous     }
126a529a6aaSEd Tanous 
127a529a6aaSEd Tanous     json["Members@odata.count"] = members.size();
12881d523a7SEd Tanous     json["Members"] = std::move(members);
12981d523a7SEd Tanous }
13081d523a7SEd Tanous 
jsonSchemaGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & schema)13181d523a7SEd Tanous inline void jsonSchemaGet(App& app, const crow::Request& req,
13281d523a7SEd Tanous                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
13381d523a7SEd Tanous                           const std::string& schema)
13481d523a7SEd Tanous {
13581d523a7SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
13681d523a7SEd Tanous     {
13781d523a7SEd Tanous         return;
13881d523a7SEd Tanous     }
13981d523a7SEd Tanous 
140a529a6aaSEd Tanous     std::error_code ec;
141a529a6aaSEd Tanous     std::filesystem::directory_iterator dirList(
142a529a6aaSEd Tanous         "/usr/share/www/redfish/v1/JsonSchemas", ec);
143a529a6aaSEd Tanous     if (ec)
14481d523a7SEd Tanous     {
145d8a5d5d8SJiaqing Zhao         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
14681d523a7SEd Tanous         return;
14781d523a7SEd Tanous     }
148a529a6aaSEd Tanous     for (const std::filesystem::path& file : dirList)
149a529a6aaSEd Tanous     {
150a529a6aaSEd Tanous         std::string filename = file.filename();
151a529a6aaSEd Tanous         std::vector<std::string> split;
152a529a6aaSEd Tanous         bmcweb::split(split, filename, '.');
153a529a6aaSEd Tanous         if (split.empty())
154a529a6aaSEd Tanous         {
155a529a6aaSEd Tanous             continue;
156a529a6aaSEd Tanous         }
157a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("Checking {}", split[0]);
158a529a6aaSEd Tanous         if (split[0] != schema)
159a529a6aaSEd Tanous         {
160a529a6aaSEd Tanous             continue;
161a529a6aaSEd Tanous         }
16281d523a7SEd Tanous 
16381d523a7SEd Tanous         nlohmann::json& json = asyncResp->res.jsonValue;
164bd79bce8SPatrick Williams         json["@odata.id"] =
165bd79bce8SPatrick Williams             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
16681d523a7SEd Tanous         json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile";
16781d523a7SEd Tanous         json["Name"] = schema + " Schema File";
16881d523a7SEd Tanous         json["Description"] = schema + " Schema File Location";
16981d523a7SEd Tanous         json["Id"] = schema;
170a529a6aaSEd Tanous         std::string schemaName = std::format("#{}.{}", schema, schema);
17181d523a7SEd Tanous         json["Schema"] = std::move(schemaName);
17281d523a7SEd Tanous         constexpr std::array<std::string_view, 1> languages{"en"};
17381d523a7SEd Tanous         json["Languages"] = languages;
17481d523a7SEd Tanous         json["Languages@odata.count"] = languages.size();
17581d523a7SEd Tanous 
17681d523a7SEd Tanous         nlohmann::json::array_t locationArray;
17781d523a7SEd Tanous         nlohmann::json::object_t locationEntry;
17881d523a7SEd Tanous         locationEntry["Language"] = "en";
179a529a6aaSEd Tanous 
180a529a6aaSEd Tanous         locationEntry["PublicationUri"] = boost::urls::format(
181a529a6aaSEd Tanous             "http://redfish.dmtf.org/schemas/v1/{}", filename);
182ef4c65b7SEd Tanous         locationEntry["Uri"] = boost::urls::format(
183a529a6aaSEd Tanous             "/redfish/v1/JsonSchemas/{}/{}", schema, filename);
18481d523a7SEd Tanous 
18581d523a7SEd Tanous         locationArray.emplace_back(locationEntry);
18681d523a7SEd Tanous 
18781d523a7SEd Tanous         json["Location"] = std::move(locationArray);
18881d523a7SEd Tanous         json["Location@odata.count"] = 1;
189a529a6aaSEd Tanous         return;
190a529a6aaSEd Tanous     }
191a529a6aaSEd Tanous     messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
192a529a6aaSEd Tanous }
193a529a6aaSEd Tanous 
jsonSchemaGetFile(const crow::Request &,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & schema,const std::string & schemaFile)194*504af5a0SPatrick Williams inline void jsonSchemaGetFile(
195*504af5a0SPatrick Williams     const crow::Request& /*req*/,
196a529a6aaSEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
197a529a6aaSEd Tanous     const std::string& schema, const std::string& schemaFile)
198a529a6aaSEd Tanous {
199a529a6aaSEd Tanous     // Sanity check the filename
200a529a6aaSEd Tanous     if (schemaFile.find_first_not_of(
201a529a6aaSEd Tanous             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") !=
202a529a6aaSEd Tanous         std::string::npos)
203a529a6aaSEd Tanous     {
204a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
205a529a6aaSEd Tanous         return;
206a529a6aaSEd Tanous     }
207a529a6aaSEd Tanous     // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json
208a529a6aaSEd Tanous     // Make sure the two paths match.
209a529a6aaSEd Tanous     if (!schemaFile.starts_with(schema))
210a529a6aaSEd Tanous     {
211a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
212a529a6aaSEd Tanous         return;
213a529a6aaSEd Tanous     }
214a529a6aaSEd Tanous     std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas");
215a529a6aaSEd Tanous     filepath /= schemaFile;
216a529a6aaSEd Tanous     if (filepath.is_relative())
217a529a6aaSEd Tanous     {
218a529a6aaSEd Tanous         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
219a529a6aaSEd Tanous         return;
220a529a6aaSEd Tanous     }
221a529a6aaSEd Tanous 
222d51c61b4SMyung Bae     crow::OpenCode ec = asyncResp->res.openFile(filepath);
223d51c61b4SMyung Bae     if (ec == crow::OpenCode::FileDoesNotExist)
224d51c61b4SMyung Bae     {
225d51c61b4SMyung Bae         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
226d51c61b4SMyung Bae         return;
227d51c61b4SMyung Bae     }
228d51c61b4SMyung Bae     if (ec == crow::OpenCode::InternalError)
229a529a6aaSEd Tanous     {
230a529a6aaSEd Tanous         BMCWEB_LOG_DEBUG("failed to read file");
231d51c61b4SMyung Bae         messages::internalError(asyncResp->res);
232a529a6aaSEd Tanous         return;
233a529a6aaSEd Tanous     }
23481d523a7SEd Tanous }
23581d523a7SEd Tanous 
requestRoutesRedfish(App & app)236f65fca6aSEd Tanous inline void requestRoutesRedfish(App& app)
237d3355c5cSEd Tanous {
238d3355c5cSEd Tanous     BMCWEB_ROUTE(app, "/redfish/")
239d3355c5cSEd Tanous         .methods(boost::beast::http::verb::get)(
240d3355c5cSEd Tanous             std::bind_front(redfishGet, std::ref(app)));
2418c623a96SEd Tanous 
242a529a6aaSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>")
243a529a6aaSEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
244a529a6aaSEd Tanous         .methods(boost::beast::http::verb::get)(jsonSchemaGetFile);
245a529a6aaSEd Tanous 
24681d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/")
2470ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFileCollection)
24881d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
24981d523a7SEd Tanous             std::bind_front(jsonSchemaGet, std::ref(app)));
25081d523a7SEd Tanous 
25181d523a7SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/")
2520ea4b4e2SEd Tanous         .privileges(redfish::privileges::getJsonSchemaFile)
25381d523a7SEd Tanous         .methods(boost::beast::http::verb::get)(
25481d523a7SEd Tanous             std::bind_front(jsonSchemaIndexGet, std::ref(app)));
255e9dd1d31SEd Tanous 
256e9dd1d31SEd Tanous     // Note, this route must always be registered last
257e9dd1d31SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2580ea4b4e2SEd Tanous         .notFound()
2590ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2600ea4b4e2SEd Tanous             std::bind_front(redfish404, std::ref(app)));
26144c70412SEd Tanous 
26244c70412SEd Tanous     BMCWEB_ROUTE(app, "/redfish/<path>")
2630ea4b4e2SEd Tanous         .methodNotAllowed()
2640ea4b4e2SEd Tanous         .privileges(redfish::privileges::privilegeSetLogin)(
2650ea4b4e2SEd Tanous             std::bind_front(redfish405, std::ref(app)));
2664c25d66eSEd Tanous }
267f65fca6aSEd Tanous 
2684c25d66eSEd Tanous } // namespace redfish
269