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