1 /* 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 17 #include "node.hpp" 18 19 #include <error_messages.hpp> 20 #include <openbmc_dbus_rest.hpp> 21 #include <utils/json_utils.hpp> 22 23 namespace redfish 24 { 25 26 using ManagedObjectType = std::vector<std::pair< 27 sdbusplus::message::object_path, 28 boost::container::flat_map< 29 std::string, 30 boost::container::flat_map< 31 std::string, sdbusplus::message::variant<bool, std::string>>>>>; 32 33 inline std::string getPrivilegeFromRoleId(boost::beast::string_view role) 34 { 35 if (role == "priv-admin") 36 { 37 return "Administrator"; 38 } 39 else if (role == "priv-callback") 40 { 41 return "Callback"; 42 } 43 else if (role == "priv-user") 44 { 45 return "User"; 46 } 47 else if (role == "priv-operator") 48 { 49 return "Operator"; 50 } 51 return ""; 52 } 53 inline std::string getRoleIdFromPrivilege(boost::beast::string_view role) 54 { 55 if (role == "Administrator") 56 { 57 return "priv-admin"; 58 } 59 else if (role == "Callback") 60 { 61 return "priv-callback"; 62 } 63 else if (role == "User") 64 { 65 return "priv-user"; 66 } 67 else if (role == "Operator") 68 { 69 return "priv-operator"; 70 } 71 return ""; 72 } 73 74 class AccountService : public Node 75 { 76 public: 77 AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/") 78 { 79 entityPrivileges = { 80 {boost::beast::http::verb::get, 81 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 82 {boost::beast::http::verb::head, {{"Login"}}}, 83 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 84 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 85 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 86 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 87 } 88 89 private: 90 void doGet(crow::Response& res, const crow::Request& req, 91 const std::vector<std::string>& params) override 92 { 93 auto asyncResp = std::make_shared<AsyncResp>(res); 94 res.jsonValue = { 95 {"@odata.context", "/redfish/v1/" 96 "$metadata#AccountService.AccountService"}, 97 {"@odata.id", "/redfish/v1/AccountService"}, 98 {"@odata.type", "#AccountService." 99 "v1_1_0.AccountService"}, 100 {"Id", "AccountService"}, 101 {"Name", "Account Service"}, 102 {"Description", "Account Service"}, 103 {"ServiceEnabled", true}, 104 {"MaxPasswordLength", 31}, 105 {"Accounts", 106 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 107 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}}; 108 109 crow::connections::systemBus->async_method_call( 110 [asyncResp]( 111 const boost::system::error_code ec, 112 const std::vector<std::pair< 113 std::string, 114 sdbusplus::message::variant<uint32_t, uint16_t, uint8_t>>>& 115 propertiesList) { 116 if (ec) 117 { 118 messages::internalError(asyncResp->res); 119 return; 120 } 121 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 122 << "properties for AccountService"; 123 for (const std::pair<std::string, 124 sdbusplus::message::variant< 125 uint32_t, uint16_t, uint8_t>>& 126 property : propertiesList) 127 { 128 if (property.first == "MinPasswordLength") 129 { 130 const uint8_t* value = 131 sdbusplus::message::variant_ns::get_if<uint8_t>( 132 &property.second); 133 if (value != nullptr) 134 { 135 asyncResp->res.jsonValue["MinPasswordLength"] = 136 *value; 137 } 138 } 139 if (property.first == "AccountUnlockTimeout") 140 { 141 const uint32_t* value = 142 sdbusplus::message::variant_ns::get_if<uint32_t>( 143 &property.second); 144 if (value != nullptr) 145 { 146 asyncResp->res.jsonValue["AccountLockoutDuration"] = 147 *value; 148 } 149 } 150 if (property.first == "MaxLoginAttemptBeforeLockout") 151 { 152 const uint16_t* value = 153 sdbusplus::message::variant_ns::get_if<uint16_t>( 154 &property.second); 155 if (value != nullptr) 156 { 157 asyncResp->res 158 .jsonValue["AccountLockoutThreshold"] = *value; 159 } 160 } 161 } 162 }, 163 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 164 "org.freedesktop.DBus.Properties", "GetAll", 165 "xyz.openbmc_project.User.AccountPolicy"); 166 } 167 void doPatch(crow::Response& res, const crow::Request& req, 168 const std::vector<std::string>& params) override 169 { 170 auto asyncResp = std::make_shared<AsyncResp>(res); 171 172 std::optional<uint32_t> unlockTimeout; 173 std::optional<uint16_t> lockoutThreshold; 174 if (!json_util::readJson(req, res, "AccountLockoutDuration", 175 unlockTimeout, "AccountLockoutThreshold", 176 lockoutThreshold)) 177 { 178 return; 179 } 180 if (unlockTimeout) 181 { 182 crow::connections::systemBus->async_method_call( 183 [asyncResp](const boost::system::error_code ec) { 184 if (ec) 185 { 186 messages::internalError(asyncResp->res); 187 return; 188 } 189 }, 190 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 191 "org.freedesktop.DBus.Properties", "Set", 192 "xyz.openbmc_project.User.AccountPolicy", 193 "AccountUnlockTimeout", 194 sdbusplus::message::variant<uint32_t>(*unlockTimeout)); 195 } 196 if (lockoutThreshold) 197 { 198 crow::connections::systemBus->async_method_call( 199 [asyncResp](const boost::system::error_code ec) { 200 if (ec) 201 { 202 messages::internalError(asyncResp->res); 203 return; 204 } 205 }, 206 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 207 "org.freedesktop.DBus.Properties", "Set", 208 "xyz.openbmc_project.User.AccountPolicy", 209 "MaxLoginAttemptBeforeLockout", 210 sdbusplus::message::variant<uint16_t>(*lockoutThreshold)); 211 } 212 } 213 }; 214 class AccountsCollection : public Node 215 { 216 public: 217 AccountsCollection(CrowApp& app) : 218 Node(app, "/redfish/v1/AccountService/Accounts/") 219 { 220 entityPrivileges = { 221 {boost::beast::http::verb::get, 222 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 223 {boost::beast::http::verb::head, {{"Login"}}}, 224 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 225 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 226 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 227 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 228 } 229 230 private: 231 void doGet(crow::Response& res, const crow::Request& req, 232 const std::vector<std::string>& params) override 233 { 234 auto asyncResp = std::make_shared<AsyncResp>(res); 235 res.jsonValue = {{"@odata.context", 236 "/redfish/v1/" 237 "$metadata#ManagerAccountCollection." 238 "ManagerAccountCollection"}, 239 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 240 {"@odata.type", "#ManagerAccountCollection." 241 "ManagerAccountCollection"}, 242 {"Name", "Accounts Collection"}, 243 {"Description", "BMC User Accounts"}}; 244 245 crow::connections::systemBus->async_method_call( 246 [asyncResp](const boost::system::error_code ec, 247 const ManagedObjectType& users) { 248 if (ec) 249 { 250 messages::internalError(asyncResp->res); 251 return; 252 } 253 254 nlohmann::json& memberArray = 255 asyncResp->res.jsonValue["Members"]; 256 memberArray = nlohmann::json::array(); 257 258 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 259 for (auto& user : users) 260 { 261 const std::string& path = 262 static_cast<const std::string&>(user.first); 263 std::size_t lastIndex = path.rfind("/"); 264 if (lastIndex == std::string::npos) 265 { 266 lastIndex = 0; 267 } 268 else 269 { 270 lastIndex += 1; 271 } 272 memberArray.push_back( 273 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 274 path.substr(lastIndex)}}); 275 } 276 }, 277 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 278 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 279 } 280 void doPost(crow::Response& res, const crow::Request& req, 281 const std::vector<std::string>& params) override 282 { 283 auto asyncResp = std::make_shared<AsyncResp>(res); 284 285 std::string username; 286 std::string password; 287 std::optional<std::string> roleId("User"); 288 std::optional<bool> enabled = true; 289 if (!json_util::readJson(req, res, "UserName", username, "Password", 290 password, "RoleId", roleId, "Enabled", 291 enabled)) 292 { 293 return; 294 } 295 296 std::string priv = getRoleIdFromPrivilege(*roleId); 297 if (priv.empty()) 298 { 299 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 300 return; 301 } 302 roleId = priv; 303 304 crow::connections::systemBus->async_method_call( 305 [asyncResp, username, password{std::move(password)}]( 306 const boost::system::error_code ec) { 307 if (ec) 308 { 309 messages::resourceAlreadyExists( 310 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 311 "UserName", username); 312 return; 313 } 314 315 if (!pamUpdatePassword(username, password)) 316 { 317 // At this point we have a user that's been created, but the 318 // password set failed. Something is wrong, so delete the 319 // user that we've already created 320 crow::connections::systemBus->async_method_call( 321 [asyncResp](const boost::system::error_code ec) { 322 if (ec) 323 { 324 messages::internalError(asyncResp->res); 325 return; 326 } 327 328 messages::invalidObject(asyncResp->res, "Password"); 329 }, 330 "xyz.openbmc_project.User.Manager", 331 "/xyz/openbmc_project/user/" + username, 332 "xyz.openbmc_project.Object.Delete", "Delete"); 333 334 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 335 return; 336 } 337 338 messages::created(asyncResp->res); 339 asyncResp->res.addHeader( 340 "Location", 341 "/redfish/v1/AccountService/Accounts/" + username); 342 }, 343 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 344 "xyz.openbmc_project.User.Manager", "CreateUser", username, 345 std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"}, 346 *roleId, *enabled); 347 } 348 }; 349 350 template <typename Callback> 351 inline void checkDbusPathExists(const std::string& path, Callback&& callback) 352 { 353 using GetObjectType = 354 std::vector<std::pair<std::string, std::vector<std::string>>>; 355 356 crow::connections::systemBus->async_method_call( 357 [callback{std::move(callback)}](const boost::system::error_code ec, 358 const GetObjectType& object_names) { 359 callback(!ec && object_names.size() != 0); 360 }, 361 "xyz.openbmc_project.ObjectMapper", 362 "/xyz/openbmc_project/object_mapper", 363 "xyz.openbmc_project.ObjectMapper", "GetObject", path, 364 std::array<std::string, 0>()); 365 } 366 367 class ManagerAccount : public Node 368 { 369 public: 370 ManagerAccount(CrowApp& app) : 371 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 372 { 373 entityPrivileges = { 374 {boost::beast::http::verb::get, 375 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 376 {boost::beast::http::verb::head, {{"Login"}}}, 377 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 378 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 379 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 380 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 381 } 382 383 private: 384 void doGet(crow::Response& res, const crow::Request& req, 385 const std::vector<std::string>& params) override 386 { 387 res.jsonValue = { 388 {"@odata.context", 389 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 390 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 391 {"Name", "User Account"}, 392 {"Description", "User Account"}, 393 {"Password", nullptr}, 394 {"RoleId", "Administrator"}}; 395 396 auto asyncResp = std::make_shared<AsyncResp>(res); 397 398 if (params.size() != 1) 399 { 400 messages::internalError(asyncResp->res); 401 return; 402 } 403 404 crow::connections::systemBus->async_method_call( 405 [asyncResp, accountName{std::string(params[0])}]( 406 const boost::system::error_code ec, 407 const ManagedObjectType& users) { 408 if (ec) 409 { 410 messages::internalError(asyncResp->res); 411 return; 412 } 413 auto userIt = users.begin(); 414 415 for (; userIt != users.end(); userIt++) 416 { 417 if (boost::ends_with(userIt->first.str, "/" + accountName)) 418 { 419 break; 420 } 421 } 422 if (userIt == users.end()) 423 { 424 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 425 accountName); 426 return; 427 } 428 for (const auto& interface : userIt->second) 429 { 430 if (interface.first == 431 "xyz.openbmc_project.User.Attributes") 432 { 433 for (const auto& property : interface.second) 434 { 435 if (property.first == "UserEnabled") 436 { 437 const bool* userEnabled = 438 sdbusplus::message::variant_ns::get_if< 439 bool>(&property.second); 440 if (userEnabled == nullptr) 441 { 442 BMCWEB_LOG_ERROR 443 << "UserEnabled wasn't a bool"; 444 messages::internalError(asyncResp->res); 445 return; 446 } 447 asyncResp->res.jsonValue["Enabled"] = 448 *userEnabled; 449 } 450 else if (property.first == 451 "UserLockedForFailedAttempt") 452 { 453 const bool* userLocked = 454 sdbusplus::message::variant_ns::get_if< 455 bool>(&property.second); 456 if (userLocked == nullptr) 457 { 458 BMCWEB_LOG_ERROR << "UserLockedForF" 459 "ailedAttempt " 460 "wasn't a bool"; 461 messages::internalError(asyncResp->res); 462 return; 463 } 464 asyncResp->res.jsonValue["Locked"] = 465 *userLocked; 466 } 467 else if (property.first == "UserPrivilege") 468 { 469 const std::string* userRolePtr = 470 sdbusplus::message::variant_ns::get_if< 471 std::string>(&property.second); 472 if (userRolePtr == nullptr) 473 { 474 BMCWEB_LOG_ERROR 475 << "UserPrivilege wasn't a " 476 "string"; 477 messages::internalError(asyncResp->res); 478 return; 479 } 480 std::string priv = 481 getPrivilegeFromRoleId(*userRolePtr); 482 if (priv.empty()) 483 { 484 BMCWEB_LOG_ERROR << "Invalid user role"; 485 messages::internalError(asyncResp->res); 486 return; 487 } 488 asyncResp->res.jsonValue["RoleId"] = priv; 489 490 asyncResp->res.jsonValue["Links"]["Role"] = { 491 {"@odata.id", "/redfish/v1/AccountService/" 492 "Roles/" + 493 priv}}; 494 } 495 } 496 } 497 } 498 499 asyncResp->res.jsonValue["@odata.id"] = 500 "/redfish/v1/AccountService/Accounts/" + accountName; 501 asyncResp->res.jsonValue["Id"] = accountName; 502 asyncResp->res.jsonValue["UserName"] = accountName; 503 }, 504 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 505 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 506 } 507 508 void doPatch(crow::Response& res, const crow::Request& req, 509 const std::vector<std::string>& params) override 510 { 511 auto asyncResp = std::make_shared<AsyncResp>(res); 512 if (params.size() != 1) 513 { 514 messages::internalError(asyncResp->res); 515 return; 516 } 517 518 std::optional<std::string> newUserName; 519 std::optional<std::string> password; 520 std::optional<bool> enabled; 521 std::optional<std::string> roleId; 522 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 523 password, "RoleId", roleId, "Enabled", 524 enabled)) 525 { 526 return; 527 } 528 529 const std::string& username = params[0]; 530 531 if (!newUserName) 532 { 533 // If the username isn't being updated, we can update the properties 534 // directly 535 updateUserProperties(asyncResp, username, password, enabled, 536 roleId); 537 return; 538 } 539 else 540 { 541 crow::connections::systemBus->async_method_call( 542 [this, asyncResp, username, password(std::move(password)), 543 roleId(std::move(roleId)), enabled(std::move(enabled)), 544 newUser{std::string(*newUserName)}]( 545 const boost::system::error_code ec) { 546 if (ec) 547 { 548 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 549 messages::resourceNotFound( 550 asyncResp->res, 551 "#ManagerAccount.v1_0_3.ManagerAccount", username); 552 return; 553 } 554 555 updateUserProperties(asyncResp, newUser, password, enabled, 556 roleId); 557 }, 558 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 559 "xyz.openbmc_project.User.Manager", "RenameUser", username, 560 *newUserName); 561 } 562 } 563 564 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 565 const std::string& username, 566 std::optional<std::string> password, 567 std::optional<bool> enabled, 568 std::optional<std::string> roleId) 569 { 570 if (password) 571 { 572 if (!pamUpdatePassword(username, *password)) 573 { 574 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 575 messages::internalError(asyncResp->res); 576 return; 577 } 578 } 579 580 if (enabled) 581 { 582 crow::connections::systemBus->async_method_call( 583 [asyncResp](const boost::system::error_code ec) { 584 if (ec) 585 { 586 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 587 messages::internalError(asyncResp->res); 588 return; 589 } 590 messages::success(asyncResp->res); 591 return; 592 }, 593 "xyz.openbmc_project.User.Manager", 594 "/xyz/openbmc_project/user/" + username, 595 "org.freedesktop.DBus.Properties", "Set", 596 "xyz.openbmc_project.User.Attributes", "UserEnabled", 597 sdbusplus::message::variant<bool>{*enabled}); 598 } 599 600 if (roleId) 601 { 602 std::string priv = getRoleIdFromPrivilege(*roleId); 603 if (priv.empty()) 604 { 605 messages::propertyValueNotInList(asyncResp->res, *roleId, 606 "RoleId"); 607 return; 608 } 609 610 crow::connections::systemBus->async_method_call( 611 [asyncResp](const boost::system::error_code ec) { 612 if (ec) 613 { 614 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 615 messages::internalError(asyncResp->res); 616 return; 617 } 618 messages::success(asyncResp->res); 619 }, 620 "xyz.openbmc_project.User.Manager", 621 "/xyz/openbmc_project/user/" + username, 622 "org.freedesktop.DBus.Properties", "Set", 623 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 624 sdbusplus::message::variant<std::string>{priv}); 625 } 626 } 627 628 void doDelete(crow::Response& res, const crow::Request& req, 629 const std::vector<std::string>& params) override 630 { 631 auto asyncResp = std::make_shared<AsyncResp>(res); 632 633 if (params.size() != 1) 634 { 635 messages::internalError(asyncResp->res); 636 return; 637 } 638 639 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 640 641 crow::connections::systemBus->async_method_call( 642 [asyncResp, username{std::move(params[0])}]( 643 const boost::system::error_code ec) { 644 if (ec) 645 { 646 messages::resourceNotFound( 647 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 648 username); 649 return; 650 } 651 652 messages::accountRemoved(asyncResp->res); 653 }, 654 "xyz.openbmc_project.User.Manager", userPath, 655 "xyz.openbmc_project.Object.Delete", "Delete"); 656 } 657 }; 658 659 } // namespace redfish 660