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