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