blob: 243ddfb48b5afa9527e660c1d52adff5a4f5cecb [file] [log] [blame]
Ed Tanous911ac312017-08-15 09:37:42 -07001#pragma once
2
Ed Tanous911ac312017-08-15 09:37:42 -07003#include <dbus/connection.hpp>
4#include <dbus/endpoint.hpp>
5#include <dbus/filter.hpp>
6#include <dbus/match.hpp>
7#include <dbus/message.hpp>
Ed Tanousba9f9a62017-10-11 16:40:35 -07008#include <persistent_data_middleware.hpp>
9#include <token_authorization_middleware.hpp>
Ed Tanous911ac312017-08-15 09:37:42 -070010#include <fstream>
Ed Tanousba9f9a62017-10-11 16:40:35 -070011#include <streambuf>
12#include <string>
Ed Tanous2a866f82017-10-25 17:46:24 -070013#include <crow/app.h>
14#include <boost/algorithm/string.hpp>
Ed Tanous3dac7492017-08-02 13:46:20 -070015namespace crow {
16namespace redfish {
17
18template <typename... Middlewares>
Ed Tanousba9f9a62017-10-11 16:40:35 -070019void get_redfish_sub_routes(Crow<Middlewares...>& app, const std::string& url,
20 nlohmann::json& j) {
Ed Tanous1c74de82017-10-26 13:58:28 -070021 std::vector<const std::string*> routes = app.get_routes(url);
22 for (auto route : routes) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070023 auto redfish_sub_route =
Ed Tanous1c74de82017-10-26 13:58:28 -070024 route->substr(url.size(), route->size() - url.size() - 1);
Ed Tanousba9f9a62017-10-11 16:40:35 -070025 // check if the route is at this level, and we didn't find and exact match
26 // also, filter out resources that start with $ to remove $metadata
27 if (!redfish_sub_route.empty() && redfish_sub_route[0] != '$' &&
28 redfish_sub_route.find('/') == std::string::npos) {
Ed Tanous1c74de82017-10-26 13:58:28 -070029 j[redfish_sub_route] = nlohmann::json{{"@odata.id", *route}};
Ed Tanous911ac312017-08-15 09:37:42 -070030 }
Ed Tanousba9f9a62017-10-11 16:40:35 -070031 }
32}
Ed Tanous911ac312017-08-15 09:37:42 -070033
Ed Tanous2a866f82017-10-25 17:46:24 -070034std::string execute_process(const char* cmd) {
35 std::array<char, 128> buffer;
36 std::string result;
37 std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
38 if (!pipe) throw std::runtime_error("popen() failed!");
39 while (!feof(pipe.get())) {
40 if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
41 result += buffer.data();
42 }
43 return result;
44}
45
Ed Tanousba9f9a62017-10-11 16:40:35 -070046template <typename... Middlewares>
47void request_routes(Crow<Middlewares...>& app) {
48 CROW_ROUTE(app, "/redfish/")
Ed Tanous911ac312017-08-15 09:37:42 -070049 .methods("GET"_method)([](const crow::request& req, crow::response& res) {
Ed Tanousba9f9a62017-10-11 16:40:35 -070050 res.json_value = {{"v1", "/redfish/v1/"}};
51 res.end();
52 });
53
54 CROW_ROUTE(app, "/redfish/v1/")
55 .methods(
56 "GET"_method)([&](const crow::request& req, crow::response& res) {
57 res.json_value = {
58 {"@odata.context", "/redfish/v1/$metadata#ServiceRoot.ServiceRoot"},
59 {"@odata.id", "/redfish/v1/"},
60 {"@odata.type", "#ServiceRoot.v1_1_1.ServiceRoot"},
61 {"Id", "RootService"},
62 {"Name", "Root Service"},
63 {"RedfishVersion", "1.1.0"},
64 {"Links",
65 {{"Sessions",
66 {{"@odata.id", "/redfish/v1/SessionService/Sessions/"}}}}}};
67
68 res.json_value["UUID"] =
69 app.template get_middleware<PersistentData::Middleware>()
70 .system_uuid;
71 get_redfish_sub_routes(app, "/redfish/v1/", res.json_value);
72 res.end();
73 });
74
Ed Tanousba9f9a62017-10-11 16:40:35 -070075 CROW_ROUTE(app, "/redfish/v1/Chassis/")
76 .methods("GET"_method)(
77 [&](const crow::request& req, crow::response& res) {
78 std::vector<std::string> entities;
79 /*std::ifstream f("~/system.json");
80
81 nlohmann::json input = nlohmann::json::parse(f);
82 for (auto it = input.begin(); it != input.end(); it++) {
83 auto value = it.value();
84 if (value["type"] == "Chassis") {
85 std::string str = value["name"];
86 entities.emplace_back(str);
87 }
88 }
89 */
Ed Tanous1c74de82017-10-26 13:58:28 -070090
Ed Tanousba9f9a62017-10-11 16:40:35 -070091 res.json_value = {
92 {"@odata.context",
93 "/redfish/v1/$metadata#ChassisCollection.ChassisCollection"},
94 {"@odata.id", "/redfish/v1/Chassis"},
95 {"@odata.type", "#ChassisCollection.ChassisCollection"},
96 {"Name", "Chassis Collection"},
97 {"Members@odata.count", entities.size()}};
98
99 get_redfish_sub_routes(app, "/redfish/v1/Chassis", res.json_value);
100 res.end();
101 });
102
103 CROW_ROUTE(app, "/redfish/v1/AccountService/")
104 .methods(
105 "GET"_method)([&](const crow::request& req, crow::response& res) {
106 res.json_value = {
107 {"@odata.context",
108 "/redfish/v1/$metadata#AccountService.AccountService"},
109 {"@odata.id", "/redfish/v1/AccountService"},
110 {"@odata.type", "#AccountService.v1_1_0.AccountService"},
111 {"Id", "AccountService"},
112 {"Name", "Account Service"},
113 {"Description", "BMC User Accounts"},
114 {"Status",
115 // TODO(ed) health rollup
116 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
117 {"ServiceEnabled", true},
118 {"MinPasswordLength", 1},
119 {"MaxPasswordLength", 20},
120 };
121 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
122 res.json_value);
123 res.end();
124 });
125
126 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/")
127 .methods("GET"_method)(
128 [&](const crow::request& req, crow::response& res) {
129 res.json_value = {
130 {"@odata.context",
131 "/redfish/v1/$metadata#RoleCollection.RoleCollection"},
132 {"@odata.id", "/redfish/v1/AccountService/Roles"},
133 {"@odata.type", "#RoleCollection.RoleCollection"},
134 {"Name", "Account Service"},
135 {"Description", "BMC User Roles"},
136 {"Members@odata.count", 1},
137 {"Members",
138 {{"@odata.id",
139 "/redfish/v1/AccountService/Roles/Administrator"}}}};
140 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
141 res.json_value);
142 res.end();
143 });
144
145 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/Administrator/")
146 .methods("GET"_method)(
147 [&](const crow::request& req, crow::response& res) {
148 res.json_value = {
149 {"@odata.context", "/redfish/v1/$metadata#Role.Role"},
150 {"@odata.id", "/redfish/v1/AccountService/Roles/Administrator"},
151 {"@odata.type", "#Role.v1_0_2.Role"},
152 {"Id", "Administrator"},
153 {"Name", "User Role"},
154 {"Description", "Administrator User Role"},
155 {"IsPredefined", true},
156 {"AssignedPrivileges",
157 {"Login", "ConfigureManager", "ConfigureUsers",
158 "ConfigureSelf", "ConfigureComponents"}}};
159 res.end();
160 });
161
162 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
163 .methods(
164 "GET"_method)([&](const crow::request& req, crow::response& res) {
Ed Tanous911ac312017-08-15 09:37:42 -0700165 boost::asio::io_service io;
166 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
167 dbus::endpoint user_list("org.openbmc.UserManager",
168 "/org/openbmc/UserManager/Users",
169 "org.openbmc.Enrol", "UserList");
170 bus->async_method_call(
171 [&](const boost::system::error_code ec,
Ed Tanousba9f9a62017-10-11 16:40:35 -0700172 const std::vector<std::string>& users) {
Ed Tanous911ac312017-08-15 09:37:42 -0700173 if (ec) {
174 res.code = 500;
175 } else {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700176 res.json_value = {
Ed Tanous911ac312017-08-15 09:37:42 -0700177 {"@odata.context",
178 "/redfish/v1/"
179 "$metadata#ManagerAccountCollection."
180 "ManagerAccountCollection"},
181 {"@odata.id", "/redfish/v1/AccountService/Accounts"},
182 {"@odata.type",
183 "#ManagerAccountCollection.ManagerAccountCollection"},
184 {"Name", "Accounts Collection"},
185 {"Description", "BMC User Accounts"},
186 {"Members@odata.count", users.size()}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700187 nlohmann::json member_array = nlohmann::json::array();
Ed Tanous911ac312017-08-15 09:37:42 -0700188 int user_index = 0;
Ed Tanousc963aa42017-10-27 16:00:19 -0700189 for (int user_index = 0; user_index < users.size();
190 user_index++) {
Ed Tanous911ac312017-08-15 09:37:42 -0700191 member_array.push_back(
Ed Tanousba9f9a62017-10-11 16:40:35 -0700192 {{"@odata.id",
193 "/redfish/v1/AccountService/Accounts/" +
Ed Tanousc963aa42017-10-27 16:00:19 -0700194 std::to_string(user_index)}});
Ed Tanous911ac312017-08-15 09:37:42 -0700195 }
Ed Tanousba9f9a62017-10-11 16:40:35 -0700196 res.json_value["Members"] = member_array;
Ed Tanous911ac312017-08-15 09:37:42 -0700197 }
198 res.end();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700199 },
200 user_list);
Ed Tanous3dac7492017-08-02 13:46:20 -0700201 });
202
203 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/<int>/")
Ed Tanousba9f9a62017-10-11 16:40:35 -0700204 .methods("GET"_method)([](const crow::request& req, crow::response& res,
205 int account_index) {
206 res.json_value = {
Ed Tanous3dac7492017-08-02 13:46:20 -0700207 {"@odata.context",
208 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
209 {"@odata.id", "/redfish/v1/AccountService/Accounts/1"},
210 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
211 {"Id", "1"},
212 {"Name", "User Account"},
213 {"Description", "User Account"},
214 {"Enabled", false},
215 {"Password", nullptr},
216 {"UserName", "anonymous"},
217 {"RoleId", "NoAccess"},
218 {"Links",
219 {{"Role",
220 {{"@odata.id", "/redfish/v1/AccountService/Roles/NoAccess"}}}}}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700221 res.end();
222 });
223
224 CROW_ROUTE(app, "/redfish/v1/SessionService/")
225 .methods(
226 "GET"_method)([&](const crow::request& req, crow::response& res) {
227 res.json_value = {
228 {"@odata.context",
229 "/redfish/v1/$metadata#SessionService.SessionService"},
230 {"@odata.id", "/redfish/v1/SessionService"},
231 {"@odata.type", "#SessionService.v1_1_1.SessionService"},
232 {"Id", "SessionService"},
233 {"Name", "SessionService"},
234 {"Description", "SessionService"},
235 {"Status",
236 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
237 {"ServiceEnabled", true},
238 // TODO(ed) converge with session timeouts once they exist
239 // Bogus number for now
240 {"SessionTimeout", 1800}};
241 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
242 res.json_value);
243 res.end();
244 });
245
246 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
247 .methods("POST"_method, "GET"_method)([&](const crow::request& req,
248 crow::response& res) {
Ed Tanousc963aa42017-10-27 16:00:19 -0700249 auto& session_store =
250 app.template get_middleware<PersistentData::Middleware>().sessions;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700251 if (req.method == "POST"_method) {
252 // call with exceptions disabled
253 auto login_credentials =
254 nlohmann::json::parse(req.body, nullptr, false);
255 if (login_credentials.is_discarded()) {
256 res.code = 400;
257 res.end();
258 return;
259 }
260 // check for username/password in the root object
261 auto user_it = login_credentials.find("UserName");
262 auto pass_it = login_credentials.find("Password");
263 if (user_it == login_credentials.end() ||
264 pass_it == login_credentials.end()) {
265 res.code = 400;
266 res.end();
267 return;
268 }
269
270 std::string username = user_it->get<const std::string>();
271 std::string password = pass_it->get<const std::string>();
272 if (username.empty() || password.empty()) {
273 res.code = 400;
274 res.end();
275 return;
276 }
277
278 if (!pam_authenticate_user(username, password)) {
279 res.code = 401;
280 res.end();
281 return;
282 }
Ed Tanousc963aa42017-10-27 16:00:19 -0700283 auto session = session_store.generate_user_session(username);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700284 res.code = 200;
285 res.add_header("X-Auth-Token", session.session_token);
286 res.json_value = {
287 {"@odata.context", "/redfish/v1/$metadata#Session"},
288 {"@odata.id",
289 "/redfish/v1/SessionService/Sessions/" + session.unique_id},
290 {"@odata.type", "#Session.v1_0_3.Session"},
291 {"Id", session.unique_id},
292 {"Name", "User Session"},
293 {"Description", "Manager User Session"},
294 {"UserName", username}};
295 } else { // assume get
Ed Tanousc963aa42017-10-27 16:00:19 -0700296 std::vector<const std::string*> session_ids =
297 session_store.get_unique_ids();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700298 res.json_value = {
299 {"@odata.context",
300 "/redfish/v1/$metadata#SessionCollection.SessionCollection"},
301 {"@odata.id", "/redfish/v1/SessionService/Sessions"},
302 {"@odata.type", "#SessionCollection.SessionCollection"},
303 {"Name", "Session Collection"},
304 {"Description", "Session Collection"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700305 {"Members@odata.count", session_ids.size()}
Ed Tanousba9f9a62017-10-11 16:40:35 -0700306
307 };
308 nlohmann::json member_array = nlohmann::json::array();
Ed Tanousc963aa42017-10-27 16:00:19 -0700309 for (auto session_uid : session_ids) {
310 member_array.push_back(
311 {{"@odata.id",
312 "/redfish/v1/SessionService/Sessions/" + *session_uid}});
Ed Tanousba9f9a62017-10-11 16:40:35 -0700313 }
314 res.json_value["Members"] = member_array;
315 }
316 res.end();
317 });
318
319 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
Ed Tanousc963aa42017-10-27 16:00:19 -0700320 .methods("GET"_method, "DELETE"_method)([&](
321 const crow::request& req, crow::response& res,
322 const std::string& session_id) {
323 auto& session_store =
324 app.template get_middleware<PersistentData::Middleware>().sessions;
Ed Tanous710adfc2017-10-24 17:04:52 -0700325 // TODO(Ed) this is inefficient
Ed Tanousc963aa42017-10-27 16:00:19 -0700326 auto session = session_store.get_session_by_uid(session_id);
Ed Tanous710adfc2017-10-24 17:04:52 -0700327
Ed Tanousc963aa42017-10-27 16:00:19 -0700328 if (session == nullptr) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700329 res.code = 404;
330 res.end();
331 return;
332 }
333 if (req.method == "DELETE"_method) {
Ed Tanousc963aa42017-10-27 16:00:19 -0700334 session_store.remove_session(session);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700335 res.code = 200;
336 } else { // assume get
337 res.json_value = {
338 {"@odata.context", "/redfish/v1/$metadata#Session.Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700339 {"@odata.id",
340 "/redfish/v1/SessionService/Sessions/" + session->unique_id},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700341 {"@odata.type", "#Session.v1_0_3.Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700342 {"Id", session->unique_id},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700343 {"Name", "User Session"},
344 {"Description", "Manager User Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700345 {"UserName", session->username}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700346 }
347 res.end();
Ed Tanous3dac7492017-08-02 13:46:20 -0700348 });
Ed Tanous2a866f82017-10-25 17:46:24 -0700349
350 CROW_ROUTE(app, "/redfish/v1/Managers/")
351 .methods("GET"_method)(
352 [&](const crow::request& req, crow::response& res) {
353 res.json_value = {
354 {"@odata.context",
355 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"},
356 {"@odata.id", "/redfish/v1/Managers"},
357 {"@odata.type", "#ManagerCollection.ManagerCollection"},
358 {"Name", "Manager Collection"},
359 {"Members@odata.count", 1},
360 {"Members", {{{"@odata.id", "/redfish/v1/Managers/openbmc"}}}}};
361 res.end();
362 });
363
364 CROW_ROUTE(app, "/redfish/v1/Managers/openbmc/")
365 .methods(
366 "GET"_method)([&](const crow::request& req, crow::response& res) {
367 time_t t = time(NULL);
368 tm* mytime = std::localtime(&t);
369 if (mytime == nullptr) {
370 res.code = 500;
371 res.end();
372 return;
373 }
374 std::array<char, 100> time_buffer;
375 std::size_t len = std::strftime(time_buffer.data(), time_buffer.size(),
376 "%FT%TZ", mytime);
377 if (len == 0) {
378 res.code = 500;
379 res.end();
380 return;
381 }
382 res.json_value = {
383 {"@odata.context", "/redfish/v1/$metadata#Manager.Manager"},
384 {"@odata.id", "/redfish/v1/Managers/openbmc"},
385 {"@odata.type", "#Manager.v1_3_0.Manager"},
386 {"Id", "openbmc"},
387 {"Name", "OpenBmc Manager"},
388 {"Description", "Baseboard Management Controller"},
389 {"UUID",
390 app.template get_middleware<PersistentData::Middleware>()
391 .system_uuid},
392 {"Model", "OpenBmc"}, // TODO(ed), get model
393 {"DateTime", time_buffer.data()},
394 {"Status",
395 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
396 {"FirmwareVersion", "1234456789"}, // TODO(ed) get fwversion
397 {"PowerState", "On"}};
398 get_redfish_sub_routes(app, "/redfish/v1/Managers/openbmc/",
399 res.json_value);
400 res.end();
401 });
402
403 CROW_ROUTE(app, "/redfish/v1/Managers/NetworkProtocol/")
404 .methods(
405 "GET"_method)([&](const crow::request& req, crow::response& res) {
406 std::array<char, HOST_NAME_MAX> hostname;
407 if (gethostname(hostname.data(), hostname.size()) != 0) {
408 res.code = 500;
409 res.end();
410 return;
411 }
412 res.json_value = {
413 {"@odata.context",
414 "/redfish/v1/"
415 "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol"},
416 {"@odata.id", "/redfish/v1/Managers/BMC/NetworkProtocol"},
417 {"@odata.type",
418 "#ManagerNetworkProtocol.v1_1_0.ManagerNetworkProtocol"},
419 {"Id", "NetworkProtocol"},
420 {"Name", "Manager Network Protocol"},
421 {"Description", "Manager Network Service"},
422 {"Status",
423 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
424 {"HostName", hostname.data()}}; // TODO(ed) get hostname
425 std::string netstat_out = execute_process("netstat -tuln");
426
427 std::map<int, const char*> service_types{{22, "SSH"},
428 {443, "HTTPS"},
429 {1900, "SSDP"},
430 {623, "IPMI"},
431 {427, "SLP"}};
432
433 std::vector<std::string> lines;
434 boost::split(lines, netstat_out, boost::is_any_of("\n"));
435 auto lines_it = lines.begin();
436 lines_it++; // skip the netstat header
437 lines_it++;
438 while (lines_it != lines.end()) {
439 std::vector<std::string> columns;
440 boost::split(columns, *lines_it, boost::is_any_of("\t "),
441 boost::token_compress_on);
442 if (columns.size() >= 5) {
443 std::size_t found = columns[3].find_last_of(":");
444 if (found != std::string::npos) {
445 std::string port_str = columns[3].substr(found + 1);
446 int port = std::stoi(port_str.c_str());
447 auto type_it = service_types.find(port);
448 if (type_it != service_types.end()) {
Ed Tanous2a866f82017-10-25 17:46:24 -0700449 res.json_value[type_it->second] = {{"ProtocolEnabled", true},
450 {"Port", port}};
451 }
452 }
453 }
454 lines_it++;
455 }
456
457 get_redfish_sub_routes(app, "/redfish/v1/", res.json_value);
458 res.end();
459 });
Ed Tanous3dac7492017-08-02 13:46:20 -0700460}
Ed Tanous911ac312017-08-15 09:37:42 -0700461} // namespace redfish
462} // namespace crow