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