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