blob: cc375914ec330d77af99f3ffad46ffaf25eee34d [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
Ed Tanous9712f8a2018-09-21 13:38:49 -0700142 std::string username;
143 std::string password;
144 boost::optional<std::string> roleId("User");
145 boost::optional<bool> enabled = true;
146 if (!json_util::readJson(req, res, "UserName", username, "Password",
147 password, "RoleId", roleId, "Enabled",
148 enabled))
Ed Tanous04ae99e2018-09-20 15:54:36 -0700149 {
150 return;
151 }
152
Ed Tanous9712f8a2018-09-21 13:38:49 -0700153 const char* priv = getRoleIdFromPrivilege(*roleId);
154 if (priv == nullptr)
Ed Tanous04ae99e2018-09-20 15:54:36 -0700155 {
156 messages::addMessageToErrorJson(
Ed Tanous9712f8a2018-09-21 13:38:49 -0700157 res.jsonValue,
158 messages::propertyValueNotInList(*roleId, "RoleId"));
159 res.result(boost::beast::http::status::bad_request);
Ed Tanous04ae99e2018-09-20 15:54:36 -0700160 return;
161 }
Ed Tanous9712f8a2018-09-21 13:38:49 -0700162 roleId = priv;
Ed Tanous04ae99e2018-09-20 15:54:36 -0700163
164 crow::connections::systemBus->async_method_call(
Ed Tanous9712f8a2018-09-21 13:38:49 -0700165 [asyncResp, username, password{std::move(password)}](
Ed Tanous04ae99e2018-09-20 15:54:36 -0700166 const boost::system::error_code ec) {
167 if (ec)
168 {
169 messages::addMessageToErrorJson(
170 asyncResp->res.jsonValue,
171 messages::resourceAlreadyExists(
172 "#ManagerAccount.v1_0_3.ManagerAccount", "UserName",
173 username));
174 asyncResp->res.result(
175 boost::beast::http::status::bad_request);
176 return;
177 }
178
179 if (!pamUpdatePassword(username, password))
180 {
181 // At this point we have a user that's been created, but the
182 // password set failed. Something is wrong, so delete the
183 // user that we've already created
184 crow::connections::systemBus->async_method_call(
185 [asyncResp](const boost::system::error_code ec) {
186 if (ec)
187 {
188 asyncResp->res.result(
189 boost::beast::http::status::
190 internal_server_error);
191 return;
192 }
193
194 asyncResp->res.result(
195 boost::beast::http::status::bad_request);
196 },
197 "xyz.openbmc_project.User.Manager",
198 "/xyz/openbmc_project/user/" + username,
199 "xyz.openbmc_project.Object.Delete", "Delete");
200
201 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
202 return;
203 }
204
205 messages::addMessageToJsonRoot(asyncResp->res.jsonValue,
206 messages::created());
207 asyncResp->res.addHeader(
208 "Location",
209 "/redfish/v1/AccountService/Accounts/" + username);
210 },
211 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
Ed Tanous9712f8a2018-09-21 13:38:49 -0700212 "xyz.openbmc_project.User.Manager", "CreateUser", username,
Ed Tanous04ae99e2018-09-20 15:54:36 -0700213 std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"},
Ed Tanous9712f8a2018-09-21 13:38:49 -0700214 *roleId, *enabled);
Ed Tanous04ae99e2018-09-20 15:54:36 -0700215 }
216
217 static const char* getRoleIdFromPrivilege(boost::beast::string_view role)
218 {
219 if (role == "Administrator")
220 {
221 return "priv-admin";
222 }
223 else if (role == "Callback")
224 {
225 return "priv-callback";
226 }
227 else if (role == "User")
228 {
229 return "priv-user";
230 }
231 else if (role == "Operator")
232 {
233 return "priv-operator";
234 }
235 return nullptr;
236 }
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700237};
238
Ed Tanousa8408792018-09-05 16:08:38 -0700239template <typename Callback>
240inline void checkDbusPathExists(const std::string& path, Callback&& callback)
241{
242 using GetObjectType =
243 std::vector<std::pair<std::string, std::vector<std::string>>>;
244
245 crow::connections::systemBus->async_method_call(
246 [callback{std::move(callback)}](const boost::system::error_code ec,
247 const GetObjectType& object_names) {
248 callback(ec || object_names.size() == 0);
249 },
250 "xyz.openbmc_project.ObjectMapper",
251 "/xyz/openbmc_project/object_mapper",
252 "xyz.openbmc_project.ObjectMapper", "GetObject", path,
253 std::array<std::string, 0>());
254}
255
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700256class ManagerAccount : public Node
257{
258 public:
259 ManagerAccount(CrowApp& app) :
260 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
261 {
262 Node::json = {{"@odata.context",
263 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
264 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
265
266 {"Name", "User Account"},
267 {"Description", "User Account"},
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700268 {"Password", nullptr},
269 {"RoleId", "Administrator"},
270 {"Links",
271 {{"Role",
272 {{"@odata.id", "/redfish/v1/AccountService/Roles/"
273 "Administrator"}}}}}};
274
275 entityPrivileges = {
276 {boost::beast::http::verb::get,
277 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
278 {boost::beast::http::verb::head, {{"Login"}}},
279 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
280 {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
281 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
282 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
283 }
284
285 private:
286 void doGet(crow::Response& res, const crow::Request& req,
287 const std::vector<std::string>& params) override
288 {
289 res.jsonValue = Node::json;
290 auto asyncResp = std::make_shared<AsyncResp>(res);
291
292 if (params.size() != 1)
293 {
294 res.result(boost::beast::http::status::internal_server_error);
295 return;
296 }
297
298 crow::connections::systemBus->async_method_call(
299 [asyncResp, accountName{std::string(params[0])}](
300 const boost::system::error_code ec,
301 const ManagedObjectType& users) {
302 if (ec)
303 {
304 asyncResp->res.result(
305 boost::beast::http::status::internal_server_error);
306 return;
307 }
308
309 for (auto& user : users)
310 {
311 const std::string& path =
312 static_cast<const std::string&>(user.first);
313 std::size_t lastIndex = path.rfind("/");
314 if (lastIndex == std::string::npos)
315 {
316 lastIndex = 0;
317 }
318 else
319 {
320 lastIndex += 1;
321 }
322 if (path.substr(lastIndex) == accountName)
323 {
Ed Tanous65b0dc32018-09-19 16:04:03 -0700324 for (const auto& interface : user.second)
325 {
326 if (interface.first ==
327 "xyz.openbmc_project.User.Attributes")
328 {
329 for (const auto& property : interface.second)
330 {
331 if (property.first == "UserEnabled")
332 {
333 const bool* userEnabled =
334 mapbox::getPtr<const bool>(
335 property.second);
336 if (userEnabled == nullptr)
337 {
338 BMCWEB_LOG_ERROR
339 << "UserEnabled wasn't a bool";
340 continue;
341 }
342 asyncResp->res.jsonValue["Enabled"] =
343 *userEnabled;
344 }
345 else if (property.first ==
346 "UserLockedForFailedAttempt")
347 {
348 const bool* userLocked =
349 mapbox::getPtr<const bool>(
350 property.second);
351 if (userLocked == nullptr)
352 {
353 BMCWEB_LOG_ERROR
354 << "UserEnabled wasn't a bool";
355 continue;
356 }
357 asyncResp->res.jsonValue["Locked"] =
358 *userLocked;
359 }
360 }
361 }
362 }
363
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700364 asyncResp->res.jsonValue["@odata.id"] =
365 "/redfish/v1/AccountService/Accounts/" +
366 accountName;
367 asyncResp->res.jsonValue["Id"] = accountName;
368 asyncResp->res.jsonValue["UserName"] = accountName;
369
370 return;
371 }
372 }
373
374 asyncResp->res.result(boost::beast::http::status::not_found);
375 },
376 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
377 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
378 }
Ed Tanousa8408792018-09-05 16:08:38 -0700379
380 void doPatch(crow::Response& res, const crow::Request& req,
381 const std::vector<std::string>& params) override
382 {
383 auto asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousa8408792018-09-05 16:08:38 -0700384 if (params.size() != 1)
385 {
386 res.result(boost::beast::http::status::internal_server_error);
387 return;
388 }
389
Ed Tanous9712f8a2018-09-21 13:38:49 -0700390 boost::optional<std::string> password;
391 boost::optional<bool> enabled;
392 if (!json_util::readJson(req, res, "Password", password, "Enabled",
393 enabled))
Ed Tanousa8408792018-09-05 16:08:38 -0700394 {
395 return;
396 }
397
398 // Check the user exists before updating the fields
399 checkDbusPathExists(
400 "/xyz/openbmc_project/users/" + params[0],
Ed Tanous9712f8a2018-09-21 13:38:49 -0700401 [username{std::string(params[0])}, password(std::move(password)),
402 enabled(std::move(enabled)), asyncResp](bool userExists) {
Ed Tanousa8408792018-09-05 16:08:38 -0700403 if (!userExists)
404 {
405 messages::addMessageToErrorJson(
406 asyncResp->res.jsonValue,
407 messages::resourceNotFound(
408 "#ManagerAccount.v1_0_3.ManagerAccount", username));
409
410 asyncResp->res.result(
411 boost::beast::http::status::not_found);
412 return;
413 }
414
Ed Tanous9712f8a2018-09-21 13:38:49 -0700415 if (password)
Ed Tanousa8408792018-09-05 16:08:38 -0700416 {
Ed Tanous9712f8a2018-09-21 13:38:49 -0700417 if (!pamUpdatePassword(username, *password))
Ed Tanousa8408792018-09-05 16:08:38 -0700418 {
Ed Tanous9712f8a2018-09-21 13:38:49 -0700419 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
Ed Tanousa8408792018-09-05 16:08:38 -0700420 asyncResp->res.result(
Ed Tanous9712f8a2018-09-21 13:38:49 -0700421 boost::beast::http::status::internal_server_error);
Ed Tanousa8408792018-09-05 16:08:38 -0700422 return;
423 }
424 }
Ed Tanous9712f8a2018-09-21 13:38:49 -0700425
426 if (enabled)
427 {
428 crow::connections::systemBus->async_method_call(
429 [asyncResp](const boost::system::error_code ec) {
430 if (ec)
431 {
432 BMCWEB_LOG_ERROR << "D-Bus responses error: "
433 << ec;
434 asyncResp->res.result(
435 boost::beast::http::status::
436 internal_server_error);
437 return;
438 }
439 // TODO Consider support polling mechanism to
440 // verify status of host and chassis after
441 // execute the requested action.
442 BMCWEB_LOG_DEBUG << "Response with no content";
443 asyncResp->res.result(
444 boost::beast::http::status::no_content);
445 },
446 "xyz.openbmc_project.User.Manager",
447 "/xyz/openbmc_project/users/" + username,
448 "org.freedesktop.DBus.Properties", "Set",
449 "xyz.openbmc_project.User.Attributes"
450 "UserEnabled",
451 sdbusplus::message::variant<bool>{*enabled});
452 }
Ed Tanousa8408792018-09-05 16:08:38 -0700453 });
454 }
Ed Tanous06e086d2018-09-19 17:19:52 -0700455
456 void doDelete(crow::Response& res, const crow::Request& req,
457 const std::vector<std::string>& params) override
458 {
459 auto asyncResp = std::make_shared<AsyncResp>(res);
460
461 if (params.size() != 1)
462 {
463 res.result(boost::beast::http::status::internal_server_error);
464 return;
465 }
466
467 const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
468
469 crow::connections::systemBus->async_method_call(
470 [asyncResp, username{std::move(params[0])}](
471 const boost::system::error_code ec) {
472 if (ec)
473 {
474 messages::addMessageToErrorJson(
475 asyncResp->res.jsonValue,
476 messages::resourceNotFound(
477 "#ManagerAccount.v1_0_3.ManagerAccount", username));
478 asyncResp->res.result(
479 boost::beast::http::status::not_found);
480 return;
481 }
482
483 messages::addMessageToJsonRoot(asyncResp->res.jsonValue,
484 messages::accountRemoved());
485 },
486 "xyz.openbmc_project.User.Manager", userPath,
487 "xyz.openbmc_project.Object.Delete", "Delete");
488 }
Ed Tanous9712f8a2018-09-21 13:38:49 -0700489}; // namespace redfish
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +0100490
Ed Tanous1abe55e2018-09-05 08:30:59 -0700491} // namespace redfish