blob: 4e5d676e9a96515e6f525e15b842d3eeeab59612 [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 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700106 messages::internalError(asyncResp->res);
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700107 return;
108 }
109
110 nlohmann::json& memberArray =
111 asyncResp->res.jsonValue["Members"];
112 memberArray = nlohmann::json::array();
113
114 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
115 for (auto& user : users)
116 {
117 const std::string& path =
118 static_cast<const std::string&>(user.first);
119 std::size_t lastIndex = path.rfind("/");
120 if (lastIndex == std::string::npos)
121 {
122 lastIndex = 0;
123 }
124 else
125 {
126 lastIndex += 1;
127 }
128 memberArray.push_back(
129 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
130 path.substr(lastIndex)}});
131 }
132 },
133 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
134 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
135 }
Ed Tanous04ae99e2018-09-20 15:54:36 -0700136 void doPost(crow::Response& res, const crow::Request& req,
137 const std::vector<std::string>& params) override
138 {
139 auto asyncResp = std::make_shared<AsyncResp>(res);
140
Ed Tanous9712f8a2018-09-21 13:38:49 -0700141 std::string username;
142 std::string password;
143 boost::optional<std::string> roleId("User");
144 boost::optional<bool> enabled = true;
145 if (!json_util::readJson(req, res, "UserName", username, "Password",
146 password, "RoleId", roleId, "Enabled",
147 enabled))
Ed Tanous04ae99e2018-09-20 15:54:36 -0700148 {
149 return;
150 }
151
Ed Tanous9712f8a2018-09-21 13:38:49 -0700152 const char* priv = getRoleIdFromPrivilege(*roleId);
153 if (priv == nullptr)
Ed Tanous04ae99e2018-09-20 15:54:36 -0700154 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700155 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
Ed Tanous04ae99e2018-09-20 15:54:36 -0700156 return;
157 }
Ed Tanous9712f8a2018-09-21 13:38:49 -0700158 roleId = priv;
Ed Tanous04ae99e2018-09-20 15:54:36 -0700159
160 crow::connections::systemBus->async_method_call(
Ed Tanous9712f8a2018-09-21 13:38:49 -0700161 [asyncResp, username, password{std::move(password)}](
Ed Tanous04ae99e2018-09-20 15:54:36 -0700162 const boost::system::error_code ec) {
163 if (ec)
164 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700165 messages::resourceAlreadyExists(
166 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
167 "UserName", username);
Ed Tanous04ae99e2018-09-20 15:54:36 -0700168 return;
169 }
170
171 if (!pamUpdatePassword(username, password))
172 {
173 // At this point we have a user that's been created, but the
174 // password set failed. Something is wrong, so delete the
175 // user that we've already created
176 crow::connections::systemBus->async_method_call(
177 [asyncResp](const boost::system::error_code ec) {
178 if (ec)
179 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700180 messages::internalError(asyncResp->res);
Ed Tanous04ae99e2018-09-20 15:54:36 -0700181 return;
182 }
183
Jason M. Billsf12894f2018-10-09 12:45:45 -0700184 messages::invalidObject(asyncResp->res, "Password");
Ed Tanous04ae99e2018-09-20 15:54:36 -0700185 },
186 "xyz.openbmc_project.User.Manager",
187 "/xyz/openbmc_project/user/" + username,
188 "xyz.openbmc_project.Object.Delete", "Delete");
189
190 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
191 return;
192 }
193
Jason M. Billsf12894f2018-10-09 12:45:45 -0700194 messages::created(asyncResp->res);
Ed Tanous04ae99e2018-09-20 15:54:36 -0700195 asyncResp->res.addHeader(
196 "Location",
197 "/redfish/v1/AccountService/Accounts/" + username);
198 },
199 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
Ed Tanous9712f8a2018-09-21 13:38:49 -0700200 "xyz.openbmc_project.User.Manager", "CreateUser", username,
Ed Tanous04ae99e2018-09-20 15:54:36 -0700201 std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"},
Ed Tanous9712f8a2018-09-21 13:38:49 -0700202 *roleId, *enabled);
Ed Tanous04ae99e2018-09-20 15:54:36 -0700203 }
204
205 static const char* getRoleIdFromPrivilege(boost::beast::string_view role)
206 {
207 if (role == "Administrator")
208 {
209 return "priv-admin";
210 }
211 else if (role == "Callback")
212 {
213 return "priv-callback";
214 }
215 else if (role == "User")
216 {
217 return "priv-user";
218 }
219 else if (role == "Operator")
220 {
221 return "priv-operator";
222 }
223 return nullptr;
224 }
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700225};
226
Ed Tanousa8408792018-09-05 16:08:38 -0700227template <typename Callback>
228inline void checkDbusPathExists(const std::string& path, Callback&& callback)
229{
230 using GetObjectType =
231 std::vector<std::pair<std::string, std::vector<std::string>>>;
232
233 crow::connections::systemBus->async_method_call(
234 [callback{std::move(callback)}](const boost::system::error_code ec,
235 const GetObjectType& object_names) {
236 callback(ec || object_names.size() == 0);
237 },
238 "xyz.openbmc_project.ObjectMapper",
239 "/xyz/openbmc_project/object_mapper",
240 "xyz.openbmc_project.ObjectMapper", "GetObject", path,
241 std::array<std::string, 0>());
242}
243
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700244class ManagerAccount : public Node
245{
246 public:
247 ManagerAccount(CrowApp& app) :
248 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
249 {
250 Node::json = {{"@odata.context",
251 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
252 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
253
254 {"Name", "User Account"},
255 {"Description", "User Account"},
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700256 {"Password", nullptr},
257 {"RoleId", "Administrator"},
258 {"Links",
259 {{"Role",
260 {{"@odata.id", "/redfish/v1/AccountService/Roles/"
261 "Administrator"}}}}}};
262
263 entityPrivileges = {
264 {boost::beast::http::verb::get,
265 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
266 {boost::beast::http::verb::head, {{"Login"}}},
267 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
268 {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
269 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
270 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
271 }
272
273 private:
274 void doGet(crow::Response& res, const crow::Request& req,
275 const std::vector<std::string>& params) override
276 {
277 res.jsonValue = Node::json;
278 auto asyncResp = std::make_shared<AsyncResp>(res);
279
280 if (params.size() != 1)
281 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700282 messages::internalError(asyncResp->res);
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700283 return;
284 }
285
286 crow::connections::systemBus->async_method_call(
287 [asyncResp, accountName{std::string(params[0])}](
288 const boost::system::error_code ec,
289 const ManagedObjectType& users) {
290 if (ec)
291 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700292 messages::internalError(asyncResp->res);
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700293 return;
294 }
295
296 for (auto& user : users)
297 {
298 const std::string& path =
299 static_cast<const std::string&>(user.first);
300 std::size_t lastIndex = path.rfind("/");
301 if (lastIndex == std::string::npos)
302 {
303 lastIndex = 0;
304 }
305 else
306 {
307 lastIndex += 1;
308 }
309 if (path.substr(lastIndex) == accountName)
310 {
Ed Tanous65b0dc32018-09-19 16:04:03 -0700311 for (const auto& interface : user.second)
312 {
313 if (interface.first ==
314 "xyz.openbmc_project.User.Attributes")
315 {
316 for (const auto& property : interface.second)
317 {
318 if (property.first == "UserEnabled")
319 {
320 const bool* userEnabled =
321 mapbox::getPtr<const bool>(
322 property.second);
323 if (userEnabled == nullptr)
324 {
325 BMCWEB_LOG_ERROR
326 << "UserEnabled wasn't a bool";
327 continue;
328 }
329 asyncResp->res.jsonValue["Enabled"] =
330 *userEnabled;
331 }
332 else if (property.first ==
333 "UserLockedForFailedAttempt")
334 {
335 const bool* userLocked =
336 mapbox::getPtr<const bool>(
337 property.second);
338 if (userLocked == nullptr)
339 {
340 BMCWEB_LOG_ERROR
341 << "UserEnabled wasn't a bool";
342 continue;
343 }
344 asyncResp->res.jsonValue["Locked"] =
345 *userLocked;
346 }
347 }
348 }
349 }
350
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700351 asyncResp->res.jsonValue["@odata.id"] =
352 "/redfish/v1/AccountService/Accounts/" +
353 accountName;
354 asyncResp->res.jsonValue["Id"] = accountName;
355 asyncResp->res.jsonValue["UserName"] = accountName;
356
357 return;
358 }
359 }
360
Jason M. Billsf12894f2018-10-09 12:45:45 -0700361 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
362 accountName);
Ed Tanousb9b2e0b2018-09-13 13:47:50 -0700363 },
364 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
365 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
366 }
Ed Tanousa8408792018-09-05 16:08:38 -0700367
368 void doPatch(crow::Response& res, const crow::Request& req,
369 const std::vector<std::string>& params) override
370 {
371 auto asyncResp = std::make_shared<AsyncResp>(res);
Ed Tanousa8408792018-09-05 16:08:38 -0700372 if (params.size() != 1)
373 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700374 messages::internalError(asyncResp->res);
Ed Tanousa8408792018-09-05 16:08:38 -0700375 return;
376 }
377
Ed Tanous9712f8a2018-09-21 13:38:49 -0700378 boost::optional<std::string> password;
379 boost::optional<bool> enabled;
380 if (!json_util::readJson(req, res, "Password", password, "Enabled",
381 enabled))
Ed Tanousa8408792018-09-05 16:08:38 -0700382 {
383 return;
384 }
385
386 // Check the user exists before updating the fields
387 checkDbusPathExists(
388 "/xyz/openbmc_project/users/" + params[0],
Ed Tanous9712f8a2018-09-21 13:38:49 -0700389 [username{std::string(params[0])}, password(std::move(password)),
390 enabled(std::move(enabled)), asyncResp](bool userExists) {
Ed Tanousa8408792018-09-05 16:08:38 -0700391 if (!userExists)
392 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700393 messages::resourceNotFound(
394 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
395 username);
Ed Tanousa8408792018-09-05 16:08:38 -0700396 return;
397 }
398
Ed Tanous9712f8a2018-09-21 13:38:49 -0700399 if (password)
Ed Tanousa8408792018-09-05 16:08:38 -0700400 {
Ed Tanous9712f8a2018-09-21 13:38:49 -0700401 if (!pamUpdatePassword(username, *password))
Ed Tanousa8408792018-09-05 16:08:38 -0700402 {
Ed Tanous9712f8a2018-09-21 13:38:49 -0700403 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
Jason M. Billsf12894f2018-10-09 12:45:45 -0700404 messages::internalError(asyncResp->res);
Ed Tanousa8408792018-09-05 16:08:38 -0700405 return;
406 }
407 }
Ed Tanous9712f8a2018-09-21 13:38:49 -0700408
409 if (enabled)
410 {
411 crow::connections::systemBus->async_method_call(
412 [asyncResp](const boost::system::error_code ec) {
413 if (ec)
414 {
415 BMCWEB_LOG_ERROR << "D-Bus responses error: "
416 << ec;
Jason M. Billsf12894f2018-10-09 12:45:45 -0700417 messages::internalError(asyncResp->res);
Ed Tanous9712f8a2018-09-21 13:38:49 -0700418 return;
419 }
420 // TODO Consider support polling mechanism to
421 // verify status of host and chassis after
422 // execute the requested action.
Jason M. Billsf12894f2018-10-09 12:45:45 -0700423 messages::success(asyncResp->res);
Ed Tanous9712f8a2018-09-21 13:38:49 -0700424 },
425 "xyz.openbmc_project.User.Manager",
426 "/xyz/openbmc_project/users/" + username,
427 "org.freedesktop.DBus.Properties", "Set",
428 "xyz.openbmc_project.User.Attributes"
429 "UserEnabled",
430 sdbusplus::message::variant<bool>{*enabled});
431 }
Ed Tanousa8408792018-09-05 16:08:38 -0700432 });
433 }
Ed Tanous06e086d2018-09-19 17:19:52 -0700434
435 void doDelete(crow::Response& res, const crow::Request& req,
436 const std::vector<std::string>& params) override
437 {
438 auto asyncResp = std::make_shared<AsyncResp>(res);
439
440 if (params.size() != 1)
441 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700442 messages::internalError(asyncResp->res);
Ed Tanous06e086d2018-09-19 17:19:52 -0700443 return;
444 }
445
446 const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
447
448 crow::connections::systemBus->async_method_call(
449 [asyncResp, username{std::move(params[0])}](
450 const boost::system::error_code ec) {
451 if (ec)
452 {
Jason M. Billsf12894f2018-10-09 12:45:45 -0700453 messages::resourceNotFound(
454 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
455 username);
Ed Tanous06e086d2018-09-19 17:19:52 -0700456 return;
457 }
458
Jason M. Billsf12894f2018-10-09 12:45:45 -0700459 messages::accountRemoved(asyncResp->res);
Ed Tanous06e086d2018-09-19 17:19:52 -0700460 },
461 "xyz.openbmc_project.User.Manager", userPath,
462 "xyz.openbmc_project.Object.Delete", "Delete");
463 }
Ed Tanous9712f8a2018-09-21 13:38:49 -0700464}; // namespace redfish
Lewanczyk, Dawid88d16c92018-02-02 14:51:09 +0100465
Ed Tanous1abe55e2018-09-05 08:30:59 -0700466} // namespace redfish