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