blob: 4de009ed901f905a4d0a609328585da101da510f [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) {
21 auto routes = app.get_routes(url);
22 for (auto& route : routes) {
23 auto redfish_sub_route =
24 route.substr(url.size(), route.size() - url.size() - 1);
25 // 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) {
29 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 */
90 res.json_value = {
91 {"@odata.context",
92 "/redfish/v1/$metadata#ChassisCollection.ChassisCollection"},
93 {"@odata.id", "/redfish/v1/Chassis"},
94 {"@odata.type", "#ChassisCollection.ChassisCollection"},
95 {"Name", "Chassis Collection"},
96 {"Members@odata.count", entities.size()}};
97
98 get_redfish_sub_routes(app, "/redfish/v1/Chassis", res.json_value);
99 res.end();
100 });
101
102 CROW_ROUTE(app, "/redfish/v1/AccountService/")
103 .methods(
104 "GET"_method)([&](const crow::request& req, crow::response& res) {
105 res.json_value = {
106 {"@odata.context",
107 "/redfish/v1/$metadata#AccountService.AccountService"},
108 {"@odata.id", "/redfish/v1/AccountService"},
109 {"@odata.type", "#AccountService.v1_1_0.AccountService"},
110 {"Id", "AccountService"},
111 {"Name", "Account Service"},
112 {"Description", "BMC User Accounts"},
113 {"Status",
114 // TODO(ed) health rollup
115 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
116 {"ServiceEnabled", true},
117 {"MinPasswordLength", 1},
118 {"MaxPasswordLength", 20},
119 };
120 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
121 res.json_value);
122 res.end();
123 });
124
125 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/")
126 .methods("GET"_method)(
127 [&](const crow::request& req, crow::response& res) {
128 res.json_value = {
129 {"@odata.context",
130 "/redfish/v1/$metadata#RoleCollection.RoleCollection"},
131 {"@odata.id", "/redfish/v1/AccountService/Roles"},
132 {"@odata.type", "#RoleCollection.RoleCollection"},
133 {"Name", "Account Service"},
134 {"Description", "BMC User Roles"},
135 {"Members@odata.count", 1},
136 {"Members",
137 {{"@odata.id",
138 "/redfish/v1/AccountService/Roles/Administrator"}}}};
139 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
140 res.json_value);
141 res.end();
142 });
143
144 CROW_ROUTE(app, "/redfish/v1/AccountService/Roles/Administrator/")
145 .methods("GET"_method)(
146 [&](const crow::request& req, crow::response& res) {
147 res.json_value = {
148 {"@odata.context", "/redfish/v1/$metadata#Role.Role"},
149 {"@odata.id", "/redfish/v1/AccountService/Roles/Administrator"},
150 {"@odata.type", "#Role.v1_0_2.Role"},
151 {"Id", "Administrator"},
152 {"Name", "User Role"},
153 {"Description", "Administrator User Role"},
154 {"IsPredefined", true},
155 {"AssignedPrivileges",
156 {"Login", "ConfigureManager", "ConfigureUsers",
157 "ConfigureSelf", "ConfigureComponents"}}};
158 res.end();
159 });
160
161 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
162 .methods(
163 "GET"_method)([&](const crow::request& req, crow::response& res) {
Ed Tanous911ac312017-08-15 09:37:42 -0700164 boost::asio::io_service io;
165 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session);
166 dbus::endpoint user_list("org.openbmc.UserManager",
167 "/org/openbmc/UserManager/Users",
168 "org.openbmc.Enrol", "UserList");
169 bus->async_method_call(
170 [&](const boost::system::error_code ec,
Ed Tanousba9f9a62017-10-11 16:40:35 -0700171 const std::vector<std::string>& users) {
Ed Tanous911ac312017-08-15 09:37:42 -0700172 if (ec) {
173 res.code = 500;
174 } else {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700175 res.json_value = {
Ed Tanous911ac312017-08-15 09:37:42 -0700176 {"@odata.context",
177 "/redfish/v1/"
178 "$metadata#ManagerAccountCollection."
179 "ManagerAccountCollection"},
180 {"@odata.id", "/redfish/v1/AccountService/Accounts"},
181 {"@odata.type",
182 "#ManagerAccountCollection.ManagerAccountCollection"},
183 {"Name", "Accounts Collection"},
184 {"Description", "BMC User Accounts"},
185 {"Members@odata.count", users.size()}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700186 nlohmann::json member_array = nlohmann::json::array();
Ed Tanous911ac312017-08-15 09:37:42 -0700187 int user_index = 0;
Ed Tanousc963aa42017-10-27 16:00:19 -0700188 for (int user_index = 0; user_index < users.size();
189 user_index++) {
Ed Tanous911ac312017-08-15 09:37:42 -0700190 member_array.push_back(
Ed Tanousba9f9a62017-10-11 16:40:35 -0700191 {{"@odata.id",
192 "/redfish/v1/AccountService/Accounts/" +
Ed Tanousc963aa42017-10-27 16:00:19 -0700193 std::to_string(user_index)}});
Ed Tanous911ac312017-08-15 09:37:42 -0700194 }
Ed Tanousba9f9a62017-10-11 16:40:35 -0700195 res.json_value["Members"] = member_array;
Ed Tanous911ac312017-08-15 09:37:42 -0700196 }
197 res.end();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700198 },
199 user_list);
Ed Tanous3dac7492017-08-02 13:46:20 -0700200 });
201
202 CROW_ROUTE(app, "/redfish/v1/AccountService/Accounts/<int>/")
Ed Tanousba9f9a62017-10-11 16:40:35 -0700203 .methods("GET"_method)([](const crow::request& req, crow::response& res,
204 int account_index) {
205 res.json_value = {
Ed Tanous3dac7492017-08-02 13:46:20 -0700206 {"@odata.context",
207 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
208 {"@odata.id", "/redfish/v1/AccountService/Accounts/1"},
209 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
210 {"Id", "1"},
211 {"Name", "User Account"},
212 {"Description", "User Account"},
213 {"Enabled", false},
214 {"Password", nullptr},
215 {"UserName", "anonymous"},
216 {"RoleId", "NoAccess"},
217 {"Links",
218 {{"Role",
219 {{"@odata.id", "/redfish/v1/AccountService/Roles/NoAccess"}}}}}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700220 res.end();
221 });
222
223 CROW_ROUTE(app, "/redfish/v1/SessionService/")
224 .methods(
225 "GET"_method)([&](const crow::request& req, crow::response& res) {
226 res.json_value = {
227 {"@odata.context",
228 "/redfish/v1/$metadata#SessionService.SessionService"},
229 {"@odata.id", "/redfish/v1/SessionService"},
230 {"@odata.type", "#SessionService.v1_1_1.SessionService"},
231 {"Id", "SessionService"},
232 {"Name", "SessionService"},
233 {"Description", "SessionService"},
234 {"Status",
235 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
236 {"ServiceEnabled", true},
237 // TODO(ed) converge with session timeouts once they exist
238 // Bogus number for now
239 {"SessionTimeout", 1800}};
240 get_redfish_sub_routes(app, "/redfish/v1/AccountService",
241 res.json_value);
242 res.end();
243 });
244
245 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/")
246 .methods("POST"_method, "GET"_method)([&](const crow::request& req,
247 crow::response& res) {
Ed Tanousc963aa42017-10-27 16:00:19 -0700248 auto& session_store =
249 app.template get_middleware<PersistentData::Middleware>().sessions;
Ed Tanousba9f9a62017-10-11 16:40:35 -0700250 if (req.method == "POST"_method) {
251 // call with exceptions disabled
252 auto login_credentials =
253 nlohmann::json::parse(req.body, nullptr, false);
254 if (login_credentials.is_discarded()) {
255 res.code = 400;
256 res.end();
257 return;
258 }
259 // check for username/password in the root object
260 auto user_it = login_credentials.find("UserName");
261 auto pass_it = login_credentials.find("Password");
262 if (user_it == login_credentials.end() ||
263 pass_it == login_credentials.end()) {
264 res.code = 400;
265 res.end();
266 return;
267 }
268
269 std::string username = user_it->get<const std::string>();
270 std::string password = pass_it->get<const std::string>();
271 if (username.empty() || password.empty()) {
272 res.code = 400;
273 res.end();
274 return;
275 }
276
277 if (!pam_authenticate_user(username, password)) {
278 res.code = 401;
279 res.end();
280 return;
281 }
Ed Tanousc963aa42017-10-27 16:00:19 -0700282 auto session = session_store.generate_user_session(username);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700283 res.code = 200;
284 res.add_header("X-Auth-Token", session.session_token);
285 res.json_value = {
286 {"@odata.context", "/redfish/v1/$metadata#Session"},
287 {"@odata.id",
288 "/redfish/v1/SessionService/Sessions/" + session.unique_id},
289 {"@odata.type", "#Session.v1_0_3.Session"},
290 {"Id", session.unique_id},
291 {"Name", "User Session"},
292 {"Description", "Manager User Session"},
293 {"UserName", username}};
294 } else { // assume get
Ed Tanousc963aa42017-10-27 16:00:19 -0700295 std::vector<const std::string*> session_ids =
296 session_store.get_unique_ids();
Ed Tanousba9f9a62017-10-11 16:40:35 -0700297 res.json_value = {
298 {"@odata.context",
299 "/redfish/v1/$metadata#SessionCollection.SessionCollection"},
300 {"@odata.id", "/redfish/v1/SessionService/Sessions"},
301 {"@odata.type", "#SessionCollection.SessionCollection"},
302 {"Name", "Session Collection"},
303 {"Description", "Session Collection"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700304 {"Members@odata.count", session_ids.size()}
Ed Tanousba9f9a62017-10-11 16:40:35 -0700305
306 };
307 nlohmann::json member_array = nlohmann::json::array();
Ed Tanousc963aa42017-10-27 16:00:19 -0700308 for (auto session_uid : session_ids) {
309 member_array.push_back(
310 {{"@odata.id",
311 "/redfish/v1/SessionService/Sessions/" + *session_uid}});
Ed Tanousba9f9a62017-10-11 16:40:35 -0700312 }
313 res.json_value["Members"] = member_array;
314 }
315 res.end();
316 });
317
318 CROW_ROUTE(app, "/redfish/v1/SessionService/Sessions/<str>/")
Ed Tanousc963aa42017-10-27 16:00:19 -0700319 .methods("GET"_method, "DELETE"_method)([&](
320 const crow::request& req, crow::response& res,
321 const std::string& session_id) {
322 auto& session_store =
323 app.template get_middleware<PersistentData::Middleware>().sessions;
Ed Tanous710adfc2017-10-24 17:04:52 -0700324 // TODO(Ed) this is inefficient
Ed Tanousc963aa42017-10-27 16:00:19 -0700325 auto session = session_store.get_session_by_uid(session_id);
Ed Tanous710adfc2017-10-24 17:04:52 -0700326
Ed Tanousc963aa42017-10-27 16:00:19 -0700327 if (session == nullptr) {
Ed Tanousba9f9a62017-10-11 16:40:35 -0700328 res.code = 404;
329 res.end();
330 return;
331 }
332 if (req.method == "DELETE"_method) {
Ed Tanousc963aa42017-10-27 16:00:19 -0700333 session_store.remove_session(session);
Ed Tanousba9f9a62017-10-11 16:40:35 -0700334 res.code = 200;
335 } else { // assume get
336 res.json_value = {
337 {"@odata.context", "/redfish/v1/$metadata#Session.Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700338 {"@odata.id",
339 "/redfish/v1/SessionService/Sessions/" + session->unique_id},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700340 {"@odata.type", "#Session.v1_0_3.Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700341 {"Id", session->unique_id},
Ed Tanousba9f9a62017-10-11 16:40:35 -0700342 {"Name", "User Session"},
343 {"Description", "Manager User Session"},
Ed Tanousc963aa42017-10-27 16:00:19 -0700344 {"UserName", session->username}};
Ed Tanousba9f9a62017-10-11 16:40:35 -0700345 }
346 res.end();
Ed Tanous3dac7492017-08-02 13:46:20 -0700347 });
Ed Tanous2a866f82017-10-25 17:46:24 -0700348
349 CROW_ROUTE(app, "/redfish/v1/Managers/")
350 .methods("GET"_method)(
351 [&](const crow::request& req, crow::response& res) {
352 res.json_value = {
353 {"@odata.context",
354 "/redfish/v1/$metadata#ManagerCollection.ManagerCollection"},
355 {"@odata.id", "/redfish/v1/Managers"},
356 {"@odata.type", "#ManagerCollection.ManagerCollection"},
357 {"Name", "Manager Collection"},
358 {"Members@odata.count", 1},
359 {"Members", {{{"@odata.id", "/redfish/v1/Managers/openbmc"}}}}};
360 res.end();
361 });
362
363 CROW_ROUTE(app, "/redfish/v1/Managers/openbmc/")
364 .methods(
365 "GET"_method)([&](const crow::request& req, crow::response& res) {
366 time_t t = time(NULL);
367 tm* mytime = std::localtime(&t);
368 if (mytime == nullptr) {
369 res.code = 500;
370 res.end();
371 return;
372 }
373 std::array<char, 100> time_buffer;
374 std::size_t len = std::strftime(time_buffer.data(), time_buffer.size(),
375 "%FT%TZ", mytime);
376 if (len == 0) {
377 res.code = 500;
378 res.end();
379 return;
380 }
381 res.json_value = {
382 {"@odata.context", "/redfish/v1/$metadata#Manager.Manager"},
383 {"@odata.id", "/redfish/v1/Managers/openbmc"},
384 {"@odata.type", "#Manager.v1_3_0.Manager"},
385 {"Id", "openbmc"},
386 {"Name", "OpenBmc Manager"},
387 {"Description", "Baseboard Management Controller"},
388 {"UUID",
389 app.template get_middleware<PersistentData::Middleware>()
390 .system_uuid},
391 {"Model", "OpenBmc"}, // TODO(ed), get model
392 {"DateTime", time_buffer.data()},
393 {"Status",
394 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
395 {"FirmwareVersion", "1234456789"}, // TODO(ed) get fwversion
396 {"PowerState", "On"}};
397 get_redfish_sub_routes(app, "/redfish/v1/Managers/openbmc/",
398 res.json_value);
399 res.end();
400 });
401
402 CROW_ROUTE(app, "/redfish/v1/Managers/NetworkProtocol/")
403 .methods(
404 "GET"_method)([&](const crow::request& req, crow::response& res) {
405 std::array<char, HOST_NAME_MAX> hostname;
406 if (gethostname(hostname.data(), hostname.size()) != 0) {
407 res.code = 500;
408 res.end();
409 return;
410 }
411 res.json_value = {
412 {"@odata.context",
413 "/redfish/v1/"
414 "$metadata#ManagerNetworkProtocol.ManagerNetworkProtocol"},
415 {"@odata.id", "/redfish/v1/Managers/BMC/NetworkProtocol"},
416 {"@odata.type",
417 "#ManagerNetworkProtocol.v1_1_0.ManagerNetworkProtocol"},
418 {"Id", "NetworkProtocol"},
419 {"Name", "Manager Network Protocol"},
420 {"Description", "Manager Network Service"},
421 {"Status",
422 {{"State", "Enabled"}, {"Health", "OK"}, {"HealthRollup", "OK"}}},
423 {"HostName", hostname.data()}}; // TODO(ed) get hostname
424 std::string netstat_out = execute_process("netstat -tuln");
425
426 std::map<int, const char*> service_types{{22, "SSH"},
427 {443, "HTTPS"},
428 {1900, "SSDP"},
429 {623, "IPMI"},
430 {427, "SLP"}};
431
432 std::vector<std::string> lines;
433 boost::split(lines, netstat_out, boost::is_any_of("\n"));
434 auto lines_it = lines.begin();
435 lines_it++; // skip the netstat header
436 lines_it++;
437 while (lines_it != lines.end()) {
438 std::vector<std::string> columns;
439 boost::split(columns, *lines_it, boost::is_any_of("\t "),
440 boost::token_compress_on);
441 if (columns.size() >= 5) {
442 std::size_t found = columns[3].find_last_of(":");
443 if (found != std::string::npos) {
444 std::string port_str = columns[3].substr(found + 1);
445 int port = std::stoi(port_str.c_str());
446 auto type_it = service_types.find(port);
447 if (type_it != service_types.end()) {
Ed Tanous2a866f82017-10-25 17:46:24 -0700448 res.json_value[type_it->second] = {{"ProtocolEnabled", true},
449 {"Port", port}};
450 }
451 }
452 }
453 lines_it++;
454 }
455
456 get_redfish_sub_routes(app, "/redfish/v1/", res.json_value);
457 res.end();
458 });
Ed Tanous3dac7492017-08-02 13:46:20 -0700459}
Ed Tanous911ac312017-08-15 09:37:42 -0700460} // namespace redfish
461} // namespace crow