blob: 3d442789e68c79eadd762b2a202c9cacf7ea65c2 [file] [log] [blame]
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +01001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16#pragma once
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +010017#include "node.hpp"
18
Ed Tanous65b0dc32018-09-19 16:04:03 -070019#include <error_messages.hpp>
Ed Tanousb9b2e0b2018-09-13 13:47:50 -070020#include <openbmc_dbus_rest.hpp>
Ed Tanousa8408792018-09-05 16:08:38 -070021#include <utils/json_utils.hpp>
Ed Tanousb9b2e0b2018-09-13 13:47:50 -070022
Ed Tanous1abe55e2018-09-05 08:30:59 -070023namespace redfish
24{
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +010025
Ed Tanousb9b2e0b2018-09-13 13:47:50 -070026using ManagedObjectType = std::vector<std::pair<
27 sdbusplus::message::object_path,
28 boost::container::flat_map<
29 std::string, boost::container::flat_map<
30 std::string, sdbusplus::message::variant<bool>>>>>;
31
Ed Tanous1abe55e2018-09-05 08:30:59 -070032class AccountService : public Node
33{
34 public:
35 AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/")
36 {
37 Node::json["@odata.id"] = "/redfish/v1/AccountService";
38 Node::json["@odata.type"] = "#AccountService.v1_1_0.AccountService";
39 Node::json["@odata.context"] =
40 "/redfish/v1/$metadata#AccountService.AccountService";
41 Node::json["Id"] = "AccountService";
42 Node::json["Description"] = "BMC User Accounts";
43 Node::json["Name"] = "Account Service";
44 Node::json["ServiceEnabled"] = true;
45 Node::json["MinPasswordLength"] = 1;
46 Node::json["MaxPasswordLength"] = 20;
47 Node::json["Accounts"]["@odata.id"] =
48 "/redfish/v1/AccountService/Accounts";
49 Node::json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
Ed Tanous3ebd75f2018-03-05 18:20:01 -080050
Ed Tanous1abe55e2018-09-05 08:30:59 -070051 entityPrivileges = {
52 {boost::beast::http::verb::get,
53 {{"ConfigureUsers"}, {"ConfigureManager"}}},
54 {boost::beast::http::verb::head, {{"Login"}}},
55 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
56 {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
57 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
58 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
59 }
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +010060
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 private:
62 void doGet(crow::Response& res, const crow::Request& req,
63 const std::vector<std::string>& params) override
64 {
65 res.jsonValue = Node::json;
66 res.end();
67 }
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +010068};
Ed Tanousb9b2e0b2018-09-13 13:47:50 -070069class AccountsCollection : public Node
70{
71 public:
72 AccountsCollection(CrowApp& app) :
73 Node(app, "/redfish/v1/AccountService/Accounts/")
74 {
75
76 Node::json = {{"@odata.context", "/redfish/v1/"
77 "$metadata#ManagerAccountCollection."
78 "ManagerAccountCollection"},
79 {"@odata.id", "/redfish/v1/AccountService/Accounts"},
80 {"@odata.type", "#ManagerAccountCollection."
81 "ManagerAccountCollection"},
82 {"Name", "Accounts Collection"},
83 {"Description", "BMC User Accounts"}};
84
85 entityPrivileges = {
86 {boost::beast::http::verb::get,
87 {{"ConfigureUsers"}, {"ConfigureManager"}}},
88 {boost::beast::http::verb::head, {{"Login"}}},
89 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
90 {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
91 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
92 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
93 }
94
95 private:
96 void doGet(crow::Response& res, const crow::Request& req,
97 const std::vector<std::string>& params) override
98 {
99 res.jsonValue = Node::json;
100 auto asyncResp = std::make_shared<AsyncResp>(res);
101 crow::connections::systemBus->async_method_call(
102 [asyncResp](const boost::system::error_code ec,
103 const ManagedObjectType& users) {
104 if (ec)
105 {
106 asyncResp->res.result(
107 boost::beast::http::status::internal_server_error);
108 return;
109 }
110
111 nlohmann::json& memberArray =
112 asyncResp->res.jsonValue["Members"];
113 memberArray = nlohmann::json::array();
114
115 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
116 for (auto& user : users)
117 {
118 const std::string& path =
119 static_cast<const std::string&>(user.first);
120 std::size_t lastIndex = path.rfind("/");
121 if (lastIndex == std::string::npos)
122 {
123 lastIndex = 0;
124 }
125 else
126 {
127 lastIndex += 1;
128 }
129 memberArray.push_back(
130 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
131 path.substr(lastIndex)}});
132 }
133 },
134 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
135 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
136 }
Ed Tanous04ae99e2018-09-20 15:54:36 -0700137 void doPost(crow::Response& res, const crow::Request& req,
138 const std::vector<std::string>& params) override
139 {
140 auto asyncResp = std::make_shared<AsyncResp>(res);
141
142 nlohmann::json patchRequest;
143 if (!json_util::processJsonFromRequest(res, req, patchRequest))
144 {
145 return;
146 }
147
148 const std::string* username = nullptr;
149 const std::string* password = nullptr;
150 // Default to user
151 std::string privilege = "priv-user";
152 // default to enabled
153 bool enabled = true;
154 for (const auto& item : patchRequest.items())
155 {
156 if (item.key() == "UserName")
157 {
158 username = item.value().get_ptr<const std::string*>();
159 if (username == nullptr)
160 {
161 messages::addMessageToErrorJson(
162 asyncResp->res.jsonValue,
163 messages::propertyValueFormatError(item.value().dump(),
164 item.key()));
165 asyncResp->res.result(
166 boost::beast::http::status::bad_request);
167 return;
168 }
169 }
170 else if (item.key() == "Enabled")
171 {
172 const bool* enabledJson = item.value().get_ptr<const bool*>();
173 if (enabledJson == nullptr)
174 {
175 messages::addMessageToErrorJson(
176 asyncResp->res.jsonValue,
177 messages::propertyValueFormatError(item.value().dump(),
178 item.key()));
179 asyncResp->res.result(
180 boost::beast::http::status::bad_request);
181 return;
182 }
183 enabled = *enabledJson;
184 }
185 else if (item.key() == "Password")
186 {
187 password = item.value().get_ptr<const std::string*>();
188 if (password == nullptr)
189 {
190 messages::addMessageToErrorJson(
191 asyncResp->res.jsonValue,
192 messages::propertyValueFormatError(item.value().dump(),
193 item.key()));
194 asyncResp->res.result(
195 boost::beast::http::status::bad_request);
196 return;
197 }
198 }
199 else if (item.key() == "RoleId")
200 {
201 const std::string* roleIdJson =
202 item.value().get_ptr<const std::string*>();
203 if (roleIdJson == nullptr)
204 {
205 messages::addMessageToErrorJson(
206 asyncResp->res.jsonValue,
207 messages::propertyValueFormatError(item.value().dump(),
208 item.key()));
209 asyncResp->res.result(
210 boost::beast::http::status::bad_request);
211 return;
212 }
213 const char* priv = getRoleIdFromPrivilege(*roleIdJson);
214 if (priv == nullptr)
215 {
216 messages::addMessageToErrorJson(
217 asyncResp->res.jsonValue,
218 messages::propertyValueNotInList(*roleIdJson,
219 item.key()));
220 asyncResp->res.result(
221 boost::beast::http::status::bad_request);
222 return;
223 }
224 privilege = priv;
225 }
226 else
227 {
228 messages::addMessageToErrorJson(
229 asyncResp->res.jsonValue,
230 messages::propertyNotWritable(item.key()));
231 asyncResp->res.result(boost::beast::http::status::bad_request);
232 return;
233 }
234 }
235
236 if (username == nullptr)
237 {
238 messages::addMessageToErrorJson(
239 asyncResp->res.jsonValue,
240 messages::createFailedMissingReqProperties("UserName"));
241 asyncResp->res.result(boost::beast::http::status::bad_request);
242 return;
243 }
244
245 if (password == nullptr)
246 {
247 messages::addMessageToErrorJson(
248 asyncResp->res.jsonValue,
249 messages::createFailedMissingReqProperties("Password"));
250 asyncResp->res.result(boost::beast::http::status::bad_request);
251 return;
252 }
253
254 crow::connections::systemBus->async_method_call(
255 [asyncResp, username{std::string(*username)},
256 password{std::string(*password)}](
257 const boost::system::error_code ec) {
258 if (ec)
259 {
260 messages::addMessageToErrorJson(
261 asyncResp->res.jsonValue,
262 messages::resourceAlreadyExists(
263 "#ManagerAccount.v1_0_3.ManagerAccount", "UserName",
264 username));
265 asyncResp->res.result(
266 boost::beast::http::status::bad_request);
267 return;
268 }
269
270 if (!pamUpdatePassword(username, password))
271 {
272 // At this point we have a user that's been created, but the
273 // password set failed. Something is wrong, so delete the
274 // user that we've already created
275 crow::connections::systemBus->async_method_call(
276 [asyncResp](const boost::system::error_code ec) {
277 if (ec)
278 {
279 asyncResp->res.result(
280 boost::beast::http::status::
281 internal_server_error);
282 return;
283 }
284
285 asyncResp->res.result(
286 boost::beast::http::status::bad_request);
287 },
288 "xyz.openbmc_project.User.Manager",
289 "/xyz/openbmc_project/user/" + username,
290 "xyz.openbmc_project.Object.Delete", "Delete");
291
292 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
293 return;
294 }
295
296 messages::addMessageToJsonRoot(asyncResp->res.jsonValue,
297 messages::created());
298 asyncResp->res.addHeader(
299 "Location",
300 "/redfish/v1/AccountService/Accounts/" + username);
301 },
302 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
303 "xyz.openbmc_project.User.Manager", "CreateUser", *username,
304 std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"},
305 privilege, enabled);
306 }
307
308 static const char* getRoleIdFromPrivilege(boost::beast::string_view role)
309 {
310 if (role == "Administrator")
311 {
312 return "priv-admin";
313 }
314 else if (role == "Callback")
315 {
316 return "priv-callback";
317 }
318 else if (role == "User")
319 {
320 return "priv-user";
321 }
322 else if (role == "Operator")
323 {
324 return "priv-operator";
325 }
326 return nullptr;
327 }
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700328};
329
Ed Tanousa8408792018-09-05 16:08:38 -0700330template <typename Callback>
331inline void checkDbusPathExists(const std::string& path, Callback&& callback)
332{
333 using GetObjectType =
334 std::vector<std::pair<std::string, std::vector<std::string>>>;
335
336 crow::connections::systemBus->async_method_call(
337 [callback{std::move(callback)}](const boost::system::error_code ec,
338 const GetObjectType& object_names) {
339 callback(ec || object_names.size() == 0);
340 },
341 "xyz.openbmc_project.ObjectMapper",
342 "/xyz/openbmc_project/object_mapper",
343 "xyz.openbmc_project.ObjectMapper", "GetObject", path,
344 std::array<std::string, 0>());
345}
346
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700347class ManagerAccount : public Node
348{
349 public:
350 ManagerAccount(CrowApp& app) :
351 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
352 {
353 Node::json = {{"@odata.context",
354 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
355 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
356
357 {"Name", "User Account"},
358 {"Description", "User Account"},
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700359 {"Password", nullptr},
360 {"RoleId", "Administrator"},
361 {"Links",
362 {{"Role",
363 {{"@odata.id", "/redfish/v1/AccountService/Roles/"
364 "Administrator"}}}}}};
365
366 entityPrivileges = {
367 {boost::beast::http::verb::get,
368 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
369 {boost::beast::http::verb::head, {{"Login"}}},
370 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
371 {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
372 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
373 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
374 }
375
376 private:
377 void doGet(crow::Response& res, const crow::Request& req,
378 const std::vector<std::string>& params) override
379 {
380 res.jsonValue = Node::json;
381 auto asyncResp = std::make_shared<AsyncResp>(res);
382
383 if (params.size() != 1)
384 {
385 res.result(boost::beast::http::status::internal_server_error);
386 return;
387 }
388
389 crow::connections::systemBus->async_method_call(
390 [asyncResp, accountName{std::string(params[0])}](
391 const boost::system::error_code ec,
392 const ManagedObjectType& users) {
393 if (ec)
394 {
395 asyncResp->res.result(
396 boost::beast::http::status::internal_server_error);
397 return;
398 }
399
400 for (auto& user : users)
401 {
402 const std::string& path =
403 static_cast<const std::string&>(user.first);
404 std::size_t lastIndex = path.rfind("/");
405 if (lastIndex == std::string::npos)
406 {
407 lastIndex = 0;
408 }
409 else
410 {
411 lastIndex += 1;
412 }
413 if (path.substr(lastIndex) == accountName)
414 {
Ed Tanous65b0dc32018-09-19 16:04:03 -0700415 for (const auto& interface : user.second)
416 {
417 if (interface.first ==
418 "xyz.openbmc_project.User.Attributes")
419 {
420 for (const auto& property : interface.second)
421 {
422 if (property.first == "UserEnabled")
423 {
424 const bool* userEnabled =
425 mapbox::getPtr<const bool>(
426 property.second);
427 if (userEnabled == nullptr)
428 {
429 BMCWEB_LOG_ERROR
430 << "UserEnabled wasn't a bool";
431 continue;
432 }
433 asyncResp->res.jsonValue["Enabled"] =
434 *userEnabled;
435 }
436 else if (property.first ==
437 "UserLockedForFailedAttempt")
438 {
439 const bool* userLocked =
440 mapbox::getPtr<const bool>(
441 property.second);
442 if (userLocked == nullptr)
443 {
444 BMCWEB_LOG_ERROR
445 << "UserEnabled wasn't a bool";
446 continue;
447 }
448 asyncResp->res.jsonValue["Locked"] =
449 *userLocked;
450 }
451 }
452 }
453 }
454
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700455 asyncResp->res.jsonValue["@odata.id"] =
456 "/redfish/v1/AccountService/Accounts/" +
457 accountName;
458 asyncResp->res.jsonValue["Id"] = accountName;
459 asyncResp->res.jsonValue["UserName"] = accountName;
460
461 return;
462 }
463 }
464
465 asyncResp->res.result(boost::beast::http::status::not_found);
466 },
467 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
468 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
469 }
Ed Tanousa8408792018-09-05 16:08:38 -0700470
471 void doPatch(crow::Response& res, const crow::Request& req,
472 const std::vector<std::string>& params) override
473 {
474 auto asyncResp = std::make_shared<AsyncResp>(res);
475
476 if (params.size() != 1)
477 {
478 res.result(boost::beast::http::status::internal_server_error);
479 return;
480 }
481
482 nlohmann::json patchRequest;
483 if (!json_util::processJsonFromRequest(res, req, patchRequest))
484 {
485 return;
486 }
487
488 // Check the user exists before updating the fields
489 checkDbusPathExists(
490 "/xyz/openbmc_project/users/" + params[0],
491 [username{std::string(params[0])},
492 patchRequest(std::move(patchRequest)),
493 asyncResp](bool userExists) {
494 if (!userExists)
495 {
496 messages::addMessageToErrorJson(
497 asyncResp->res.jsonValue,
498 messages::resourceNotFound(
499 "#ManagerAccount.v1_0_3.ManagerAccount", username));
500
501 asyncResp->res.result(
502 boost::beast::http::status::not_found);
503 return;
504 }
505
506 for (const auto& item : patchRequest.items())
507 {
508 if (item.key() == "Password")
509 {
510 const std::string* passStr =
511 item.value().get_ptr<const std::string*>();
512 if (passStr == nullptr)
513 {
514 messages::addMessageToErrorJson(
515 asyncResp->res.jsonValue,
516 messages::propertyValueFormatError(
517 item.value().dump(), "Password"));
518 return;
519 }
520 BMCWEB_LOG_DEBUG << "Updating user: " << username
521 << " to password " << *passStr;
522 if (!pamUpdatePassword(username, *passStr))
523 {
524 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
525 asyncResp->res.result(boost::beast::http::status::
526 internal_server_error);
527 return;
528 }
529 }
530 else if (item.key() == "Enabled")
531 {
532 const bool* enabledBool =
533 item.value().get_ptr<const bool*>();
534
535 if (enabledBool == nullptr)
536 {
537 messages::addMessageToErrorJson(
538 asyncResp->res.jsonValue,
539 messages::propertyValueFormatError(
540 item.value().dump(), "Enabled"));
541 return;
542 }
543 crow::connections::systemBus->async_method_call(
544 [asyncResp](const boost::system::error_code ec) {
545 if (ec)
546 {
547 BMCWEB_LOG_ERROR
548 << "D-Bus responses error: " << ec;
549 asyncResp->res.result(
550 boost::beast::http::status::
551 internal_server_error);
552 return;
553 }
554 // TODO Consider support polling mechanism to
555 // verify status of host and chassis after
556 // execute the requested action.
557 BMCWEB_LOG_DEBUG << "Response with no content";
558 asyncResp->res.result(
559 boost::beast::http::status::no_content);
560 },
561 "xyz.openbmc_project.User.Manager",
562 "/xyz/openbmc_project/users/" + username,
563 "org.freedesktop.DBus.Properties", "Set",
564 "xyz.openbmc_project.User.Attributes"
565 "UserEnabled",
566 sdbusplus::message::variant<bool>{*enabledBool});
567 }
568 else
569 {
570 messages::addMessageToErrorJson(
571 asyncResp->res.jsonValue,
572 messages::propertyNotWritable(item.key()));
573 asyncResp->res.result(
574 boost::beast::http::status::bad_request);
575 return;
576 }
577 }
578 });
579 }
Ed Tanous06e086d2018-09-19 17:19:52 -0700580
581 void doDelete(crow::Response& res, const crow::Request& req,
582 const std::vector<std::string>& params) override
583 {
584 auto asyncResp = std::make_shared<AsyncResp>(res);
585
586 if (params.size() != 1)
587 {
588 res.result(boost::beast::http::status::internal_server_error);
589 return;
590 }
591
592 const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
593
594 crow::connections::systemBus->async_method_call(
595 [asyncResp, username{std::move(params[0])}](
596 const boost::system::error_code ec) {
597 if (ec)
598 {
599 messages::addMessageToErrorJson(
600 asyncResp->res.jsonValue,
601 messages::resourceNotFound(
602 "#ManagerAccount.v1_0_3.ManagerAccount", username));
603 asyncResp->res.result(
604 boost::beast::http::status::not_found);
605 return;
606 }
607
608 messages::addMessageToJsonRoot(asyncResp->res.jsonValue,
609 messages::accountRemoved());
610 },
611 "xyz.openbmc_project.User.Manager", userPath,
612 "xyz.openbmc_project.Object.Delete", "Delete");
613 }
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700614};
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +0100615
Ed Tanous1abe55e2018-09-05 08:30:59 -0700616} // namespace redfish