xref: /openbmc/bmcweb/redfish-core/lib/redfish_v1.hpp (revision 04c39938e1ecb9d249a425039b5684da78db382a)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "error_messages.hpp"
8 #include "http_request.hpp"
9 #include "http_response.hpp"
10 #include "logging.hpp"
11 #include "query.hpp"
12 #include "registries/privilege_registry.hpp"
13 #include "str_utility.hpp"
14 #include "utils/json_utils.hpp"
15 
16 #include <boost/beast/http/field.hpp>
17 #include <boost/beast/http/status.hpp>
18 #include <boost/beast/http/verb.hpp>
19 #include <boost/url/format.hpp>
20 
21 #include <array>
22 #include <filesystem>
23 #include <format>
24 #include <functional>
25 #include <memory>
26 #include <ranges>
27 #include <string>
28 #include <system_error>
29 #include <utility>
30 #include <vector>
31 
32 namespace redfish
33 {
34 
redfishGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)35 inline void redfishGet(App& app, const crow::Request& req,
36                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
37 {
38     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
39     {
40         return;
41     }
42     asyncResp->res.jsonValue["v1"] = "/redfish/v1/";
43 }
44 
redfish404(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & path)45 inline void redfish404(App& app, const crow::Request& req,
46                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
47                        const std::string& path)
48 {
49     asyncResp->res.addHeader(boost::beast::http::field::allow, "");
50 
51     // If we fall to this route, we didn't have a more specific route, so return
52     // 404
53     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
54     {
55         return;
56     }
57 
58     BMCWEB_LOG_WARNING("404 on path {}", path);
59 
60     std::string name = req.url().segments().back();
61     // Note, if we hit the wildcard route, we don't know the "type" the user was
62     // actually requesting, but giving them a return with an empty string is
63     // still better than nothing.
64     messages::resourceNotFound(asyncResp->res, "", name);
65 }
66 
redfish405(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & path)67 inline void redfish405(App& app, const crow::Request& req,
68                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
69                        const std::string& path)
70 {
71     // If we fall to this route, we didn't have a more specific route, so return
72     // 405
73     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
74     {
75         return;
76     }
77 
78     BMCWEB_LOG_WARNING("405 on path {}", path);
79     asyncResp->res.result(boost::beast::http::status::method_not_allowed);
80     if (req.method() == boost::beast::http::verb::delete_)
81     {
82         messages::resourceCannotBeDeleted(asyncResp->res);
83     }
84     else
85     {
86         messages::operationNotAllowed(asyncResp->res);
87     }
88 }
89 
jsonSchemaIndexGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)90 inline void jsonSchemaIndexGet(
91     App& app, const crow::Request& req,
92     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
93 {
94     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
95     {
96         return;
97     }
98     nlohmann::json& json = asyncResp->res.jsonValue;
99     json["@odata.id"] = "/redfish/v1/JsonSchemas";
100     json["@odata.type"] = "#JsonSchemaFileCollection.JsonSchemaFileCollection";
101     json["Name"] = "JsonSchemaFile Collection";
102     json["Description"] = "Collection of JsonSchemaFiles";
103 
104     std::error_code ec;
105     std::filesystem::directory_iterator dirList(
106         "/usr/share/www/redfish/v1/JsonSchemas", ec);
107     if (ec)
108     {
109         messages::internalError(asyncResp->res);
110         return;
111     }
112 
113     std::vector<std::string> schemaNames;
114     for (const std::filesystem::path& file : dirList)
115     {
116         std::string filename = file.filename();
117         std::vector<std::string> split;
118         bmcweb::split(split, filename, '.');
119         if (split.empty())
120         {
121             continue;
122         }
123         schemaNames.emplace_back(split[0]);
124     }
125     std::ranges::sort(schemaNames, AlphanumLess<std::string>());
126 
127     nlohmann::json::array_t members;
128     for (const std::string& schema : schemaNames)
129     {
130         nlohmann::json::object_t member;
131         member["@odata.id"] =
132             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
133         members.emplace_back(std::move(member));
134     }
135     json["Members@odata.count"] = members.size();
136     json["Members"] = std::move(members);
137 }
138 
jsonSchemaGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & schema)139 inline void jsonSchemaGet(App& app, const crow::Request& req,
140                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
141                           const std::string& schema)
142 {
143     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
144     {
145         return;
146     }
147 
148     std::error_code ec;
149     std::filesystem::directory_iterator dirList(
150         "/usr/share/www/redfish/v1/JsonSchemas", ec);
151     if (ec)
152     {
153         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
154         return;
155     }
156     for (const std::filesystem::path& file : dirList)
157     {
158         std::string filename = file.filename();
159         std::vector<std::string> split;
160         bmcweb::split(split, filename, '.');
161         if (split.empty())
162         {
163             continue;
164         }
165         BMCWEB_LOG_DEBUG("Checking {}", split[0]);
166         if (split[0] != schema)
167         {
168             continue;
169         }
170 
171         nlohmann::json& json = asyncResp->res.jsonValue;
172         json["@odata.id"] =
173             boost::urls::format("/redfish/v1/JsonSchemas/{}", schema);
174         json["@odata.type"] = "#JsonSchemaFile.v1_0_2.JsonSchemaFile";
175         json["Name"] = schema + " Schema File";
176         json["Description"] = schema + " Schema File Location";
177         json["Id"] = schema;
178         std::string schemaName = std::format("#{}.{}", schema, schema);
179         json["Schema"] = std::move(schemaName);
180         constexpr std::array<std::string_view, 1> languages{"en"};
181         json["Languages"] = languages;
182         json["Languages@odata.count"] = languages.size();
183 
184         nlohmann::json::array_t locationArray;
185         nlohmann::json::object_t locationEntry;
186         locationEntry["Language"] = "en";
187 
188         locationEntry["PublicationUri"] = boost::urls::format(
189             "http://redfish.dmtf.org/schemas/v1/{}", filename);
190         locationEntry["Uri"] = boost::urls::format(
191             "/redfish/v1/JsonSchemas/{}/{}", schema, filename);
192 
193         locationArray.emplace_back(locationEntry);
194 
195         json["Location"] = std::move(locationArray);
196         json["Location@odata.count"] = 1;
197         return;
198     }
199     messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
200 }
201 
jsonSchemaGetFile(const crow::Request &,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & schema,const std::string & schemaFile)202 inline void jsonSchemaGetFile(
203     const crow::Request& /*req*/,
204     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
205     const std::string& schema, const std::string& schemaFile)
206 {
207     // Sanity check the filename
208     if (schemaFile.find_first_not_of(
209             "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.") !=
210         std::string::npos)
211     {
212         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
213         return;
214     }
215     // Schema path should look like /redfish/v1/JsonSchemas/Foo/Foo.x.json
216     // Make sure the two paths match.
217     if (!schemaFile.starts_with(schema))
218     {
219         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
220         return;
221     }
222     std::filesystem::path filepath("/usr/share/www/redfish/v1/JsonSchemas");
223     filepath /= schemaFile;
224     if (filepath.is_relative())
225     {
226         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
227         return;
228     }
229 
230     crow::OpenCode ec = asyncResp->res.openFile(filepath);
231     if (ec == crow::OpenCode::FileDoesNotExist)
232     {
233         messages::resourceNotFound(asyncResp->res, "JsonSchemaFile", schema);
234         return;
235     }
236     if (ec == crow::OpenCode::InternalError)
237     {
238         BMCWEB_LOG_DEBUG("failed to read file");
239         messages::internalError(asyncResp->res);
240         return;
241     }
242 }
243 
requestRoutesRedfish(App & app)244 inline void requestRoutesRedfish(App& app)
245 {
246     BMCWEB_ROUTE(app, "/redfish/")
247         .methods(boost::beast::http::verb::get)(
248             std::bind_front(redfishGet, std::ref(app)));
249 
250     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/<str>")
251         .privileges(redfish::privileges::getJsonSchemaFile)
252         .methods(boost::beast::http::verb::get)(jsonSchemaGetFile);
253 
254     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/<str>/")
255         .privileges(redfish::privileges::getJsonSchemaFileCollection)
256         .methods(boost::beast::http::verb::get)(
257             std::bind_front(jsonSchemaGet, std::ref(app)));
258 
259     BMCWEB_ROUTE(app, "/redfish/v1/JsonSchemas/")
260         .privileges(redfish::privileges::getJsonSchemaFile)
261         .methods(boost::beast::http::verb::get)(
262             std::bind_front(jsonSchemaIndexGet, std::ref(app)));
263 
264     // Note, this route must always be registered last
265     BMCWEB_ROUTE(app, "/redfish/<path>")
266         .notFound()
267         .privileges(redfish::privileges::privilegeSetLogin)(
268             std::bind_front(redfish404, std::ref(app)));
269 
270     BMCWEB_ROUTE(app, "/redfish/<path>")
271         .methodNotAllowed()
272         .privileges(redfish::privileges::privilegeSetLogin)(
273             std::bind_front(redfish405, std::ref(app)));
274 }
275 
276 } // namespace redfish
277