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 18 #include "app.hpp" 19 #include "dbus_utility.hpp" 20 #include "error_messages.hpp" 21 #include "generated/enums/account_service.hpp" 22 #include "openbmc_dbus_rest.hpp" 23 #include "persistent_data.hpp" 24 #include "query.hpp" 25 #include "registries/privilege_registry.hpp" 26 #include "utils/dbus_utils.hpp" 27 #include "utils/json_utils.hpp" 28 29 #include <sdbusplus/asio/property.hpp> 30 #include <sdbusplus/unpack_properties.hpp> 31 32 #include <array> 33 #include <optional> 34 #include <ranges> 35 #include <string> 36 #include <string_view> 37 #include <vector> 38 39 namespace redfish 40 { 41 42 constexpr const char* ldapConfigObjectName = 43 "/xyz/openbmc_project/user/ldap/openldap"; 44 constexpr const char* adConfigObject = 45 "/xyz/openbmc_project/user/ldap/active_directory"; 46 47 constexpr const char* rootUserDbusPath = "/xyz/openbmc_project/user/"; 48 constexpr const char* ldapRootObject = "/xyz/openbmc_project/user/ldap"; 49 constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config"; 50 constexpr const char* ldapConfigInterface = 51 "xyz.openbmc_project.User.Ldap.Config"; 52 constexpr const char* ldapCreateInterface = 53 "xyz.openbmc_project.User.Ldap.Create"; 54 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable"; 55 constexpr const char* ldapPrivMapperInterface = 56 "xyz.openbmc_project.User.PrivilegeMapper"; 57 58 struct LDAPRoleMapData 59 { 60 std::string groupName; 61 std::string privilege; 62 }; 63 64 struct LDAPConfigData 65 { 66 std::string uri; 67 std::string bindDN; 68 std::string baseDN; 69 std::string searchScope; 70 std::string serverType; 71 bool serviceEnabled = false; 72 std::string userNameAttribute; 73 std::string groupAttribute; 74 std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList; 75 }; 76 77 inline std::string getRoleIdFromPrivilege(std::string_view role) 78 { 79 if (role == "priv-admin") 80 { 81 return "Administrator"; 82 } 83 if (role == "priv-user") 84 { 85 return "ReadOnly"; 86 } 87 if (role == "priv-operator") 88 { 89 return "Operator"; 90 } 91 return ""; 92 } 93 inline std::string getPrivilegeFromRoleId(std::string_view role) 94 { 95 if (role == "Administrator") 96 { 97 return "priv-admin"; 98 } 99 if (role == "ReadOnly") 100 { 101 return "priv-user"; 102 } 103 if (role == "Operator") 104 { 105 return "priv-operator"; 106 } 107 return ""; 108 } 109 110 /** 111 * @brief Maps user group names retrieved from D-Bus object to 112 * Account Types. 113 * 114 * @param[in] userGroups List of User groups 115 * @param[out] res AccountTypes populated 116 * 117 * @return true in case of success, false if UserGroups contains 118 * invalid group name(s). 119 */ 120 inline bool translateUserGroup(const std::vector<std::string>& userGroups, 121 crow::Response& res) 122 { 123 std::vector<std::string> accountTypes; 124 for (const auto& userGroup : userGroups) 125 { 126 if (userGroup == "redfish") 127 { 128 accountTypes.emplace_back("Redfish"); 129 accountTypes.emplace_back("WebUI"); 130 } 131 else if (userGroup == "ipmi") 132 { 133 accountTypes.emplace_back("IPMI"); 134 } 135 else if (userGroup == "ssh") 136 { 137 accountTypes.emplace_back("ManagerConsole"); 138 } 139 else if (userGroup == "hostconsole") 140 { 141 // The hostconsole group controls who can access the host console 142 // port via ssh and websocket. 143 accountTypes.emplace_back("HostConsole"); 144 } 145 else if (userGroup == "web") 146 { 147 // 'web' is one of the valid groups in the UserGroups property of 148 // the user account in the D-Bus object. This group is currently not 149 // doing anything, and is considered to be equivalent to 'redfish'. 150 // 'redfish' user group is mapped to 'Redfish'and 'WebUI' 151 // AccountTypes, so do nothing here... 152 } 153 else 154 { 155 // Invalid user group name. Caller throws an exception. 156 return false; 157 } 158 } 159 160 res.jsonValue["AccountTypes"] = std::move(accountTypes); 161 return true; 162 } 163 164 /** 165 * @brief Builds User Groups from the Account Types 166 * 167 * @param[in] asyncResp Async Response 168 * @param[in] accountTypes List of Account Types 169 * @param[out] userGroups List of User Groups mapped from Account Types 170 * 171 * @return true if Account Types mapped to User Groups, false otherwise. 172 */ 173 inline bool 174 getUserGroupFromAccountType(crow::Response& res, 175 const std::vector<std::string>& accountTypes, 176 std::vector<std::string>& userGroups) 177 { 178 // Need both Redfish and WebUI Account Types to map to 'redfish' User Group 179 bool redfishType = false; 180 bool webUIType = false; 181 182 for (const auto& accountType : accountTypes) 183 { 184 if (accountType == "Redfish") 185 { 186 redfishType = true; 187 } 188 else if (accountType == "WebUI") 189 { 190 webUIType = true; 191 } 192 else if (accountType == "IPMI") 193 { 194 userGroups.emplace_back("ipmi"); 195 } 196 else if (accountType == "HostConsole") 197 { 198 userGroups.emplace_back("hostconsole"); 199 } 200 else if (accountType == "ManagerConsole") 201 { 202 userGroups.emplace_back("ssh"); 203 } 204 else 205 { 206 // Invalid Account Type 207 messages::propertyValueNotInList(res, "AccountTypes", accountType); 208 return false; 209 } 210 } 211 212 // Both Redfish and WebUI Account Types are needed to PATCH 213 if (redfishType ^ webUIType) 214 { 215 BMCWEB_LOG_ERROR( 216 "Missing Redfish or WebUI Account Type to set redfish User Group"); 217 messages::strictAccountTypes(res, "AccountTypes"); 218 return false; 219 } 220 221 if (redfishType && webUIType) 222 { 223 userGroups.emplace_back("redfish"); 224 } 225 226 return true; 227 } 228 229 /** 230 * @brief Sets UserGroups property of the user based on the Account Types 231 * 232 * @param[in] accountTypes List of User Account Types 233 * @param[in] asyncResp Async Response 234 * @param[in] dbusObjectPath D-Bus Object Path 235 * @param[in] userSelf true if User is updating OWN Account Types 236 */ 237 inline void 238 patchAccountTypes(const std::vector<std::string>& accountTypes, 239 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 240 const std::string& dbusObjectPath, bool userSelf) 241 { 242 // Check if User is disabling own Redfish Account Type 243 if (userSelf && 244 (accountTypes.cend() == 245 std::find(accountTypes.cbegin(), accountTypes.cend(), "Redfish"))) 246 { 247 BMCWEB_LOG_ERROR( 248 "User disabling OWN Redfish Account Type is not allowed"); 249 messages::strictAccountTypes(asyncResp->res, "AccountTypes"); 250 return; 251 } 252 253 std::vector<std::string> updatedUserGroups; 254 if (!getUserGroupFromAccountType(asyncResp->res, accountTypes, 255 updatedUserGroups)) 256 { 257 // Problem in mapping Account Types to User Groups, Error already 258 // logged. 259 return; 260 } 261 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 262 dbusObjectPath, "xyz.openbmc_project.User.Attributes", 263 "UserGroups", "AccountTypes", updatedUserGroups); 264 } 265 266 inline void userErrorMessageHandler( 267 const sd_bus_error* e, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 268 const std::string& newUser, const std::string& username) 269 { 270 if (e == nullptr) 271 { 272 messages::internalError(asyncResp->res); 273 return; 274 } 275 276 const char* errorMessage = e->name; 277 if (strcmp(errorMessage, 278 "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0) 279 { 280 messages::resourceAlreadyExists(asyncResp->res, "ManagerAccount", 281 "UserName", newUser); 282 } 283 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 284 "UserNameDoesNotExist") == 0) 285 { 286 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 287 } 288 else if ((strcmp(errorMessage, 289 "xyz.openbmc_project.Common.Error.InvalidArgument") == 290 0) || 291 (strcmp( 292 errorMessage, 293 "xyz.openbmc_project.User.Common.Error.UserNameGroupFail") == 294 0)) 295 { 296 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 297 } 298 else if (strcmp(errorMessage, 299 "xyz.openbmc_project.User.Common.Error.NoResource") == 0) 300 { 301 messages::createLimitReachedForResource(asyncResp->res); 302 } 303 else 304 { 305 BMCWEB_LOG_ERROR("DBUS response error {}", errorMessage); 306 messages::internalError(asyncResp->res); 307 } 308 } 309 310 inline void parseLDAPConfigData(nlohmann::json& jsonResponse, 311 const LDAPConfigData& confData, 312 const std::string& ldapType) 313 { 314 nlohmann::json::object_t ldap; 315 ldap["ServiceEnabled"] = confData.serviceEnabled; 316 nlohmann::json::array_t serviceAddresses; 317 serviceAddresses.emplace_back(confData.uri); 318 ldap["ServiceAddresses"] = std::move(serviceAddresses); 319 320 nlohmann::json::object_t authentication; 321 authentication["AuthenticationType"] = 322 account_service::AuthenticationTypes::UsernameAndPassword; 323 authentication["Username"] = confData.bindDN; 324 authentication["Password"] = nullptr; 325 ldap["Authentication"] = std::move(authentication); 326 327 nlohmann::json::object_t ldapService; 328 nlohmann::json::object_t searchSettings; 329 nlohmann::json::array_t baseDistinguishedNames; 330 baseDistinguishedNames.emplace_back(confData.baseDN); 331 332 searchSettings["BaseDistinguishedNames"] = 333 std::move(baseDistinguishedNames); 334 searchSettings["UsernameAttribute"] = confData.userNameAttribute; 335 searchSettings["GroupsAttribute"] = confData.groupAttribute; 336 ldapService["SearchSettings"] = std::move(searchSettings); 337 ldap["LDAPService"] = std::move(ldapService); 338 339 nlohmann::json::array_t roleMapArray; 340 for (const auto& obj : confData.groupRoleList) 341 { 342 BMCWEB_LOG_DEBUG("Pushing the data groupName={}", obj.second.groupName); 343 344 nlohmann::json::object_t remoteGroup; 345 remoteGroup["RemoteGroup"] = obj.second.groupName; 346 remoteGroup["LocalRole"] = getRoleIdFromPrivilege(obj.second.privilege); 347 roleMapArray.emplace_back(std::move(remoteGroup)); 348 } 349 350 ldap["RemoteRoleMapping"] = std::move(roleMapArray); 351 352 jsonResponse[ldapType].update(ldap); 353 } 354 355 /** 356 * @brief validates given JSON input and then calls appropriate method to 357 * create, to delete or to set Rolemapping object based on the given input. 358 * 359 */ 360 inline void handleRoleMapPatch( 361 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 362 const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData, 363 const std::string& serverType, const std::vector<nlohmann::json>& input) 364 { 365 for (size_t index = 0; index < input.size(); index++) 366 { 367 const nlohmann::json& thisJson = input[index]; 368 369 if (thisJson.is_null()) 370 { 371 // delete the existing object 372 if (index < roleMapObjData.size()) 373 { 374 crow::connections::systemBus->async_method_call( 375 [asyncResp, roleMapObjData, serverType, 376 index](const boost::system::error_code& ec) { 377 if (ec) 378 { 379 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 380 messages::internalError(asyncResp->res); 381 return; 382 } 383 asyncResp->res.jsonValue[serverType]["RemoteRoleMapping"] 384 [index] = nullptr; 385 }, 386 ldapDbusService, roleMapObjData[index].first, 387 "xyz.openbmc_project.Object.Delete", "Delete"); 388 } 389 else 390 { 391 BMCWEB_LOG_ERROR("Can't delete the object"); 392 messages::propertyValueTypeError(asyncResp->res, thisJson, 393 "RemoteRoleMapping/" + 394 std::to_string(index)); 395 return; 396 } 397 } 398 else if (thisJson.empty()) 399 { 400 // Don't do anything for the empty objects,parse next json 401 // eg {"RemoteRoleMapping",[{}]} 402 } 403 else 404 { 405 // update/create the object 406 std::optional<std::string> remoteGroup; 407 std::optional<std::string> localRole; 408 409 // This is a copy, but it's required in this case because of how 410 // readJson is structured 411 nlohmann::json thisJsonCopy = thisJson; 412 if (!json_util::readJson(thisJsonCopy, asyncResp->res, 413 "RemoteGroup", remoteGroup, "LocalRole", 414 localRole)) 415 { 416 continue; 417 } 418 419 // Update existing RoleMapping Object 420 if (index < roleMapObjData.size()) 421 { 422 BMCWEB_LOG_DEBUG("Update Role Map Object"); 423 // If "RemoteGroup" info is provided 424 if (remoteGroup) 425 { 426 setDbusProperty( 427 asyncResp, ldapDbusService, roleMapObjData[index].first, 428 "xyz.openbmc_project.User.PrivilegeMapperEntry", 429 "GroupName", 430 std::format("RemoteRoleMapping/{}/RemoteGroup", index), 431 *remoteGroup); 432 } 433 434 // If "LocalRole" info is provided 435 if (localRole) 436 { 437 setDbusProperty( 438 asyncResp, ldapDbusService, roleMapObjData[index].first, 439 "xyz.openbmc_project.User.PrivilegeMapperEntry", 440 "Privilege", 441 std::format("RemoteRoleMapping/{}/LocalRole", index), 442 *localRole); 443 } 444 } 445 // Create a new RoleMapping Object. 446 else 447 { 448 BMCWEB_LOG_DEBUG( 449 "setRoleMappingProperties: Creating new Object"); 450 std::string pathString = "RemoteRoleMapping/" + 451 std::to_string(index); 452 453 if (!localRole) 454 { 455 messages::propertyMissing(asyncResp->res, 456 pathString + "/LocalRole"); 457 continue; 458 } 459 if (!remoteGroup) 460 { 461 messages::propertyMissing(asyncResp->res, 462 pathString + "/RemoteGroup"); 463 continue; 464 } 465 466 std::string dbusObjectPath; 467 if (serverType == "ActiveDirectory") 468 { 469 dbusObjectPath = adConfigObject; 470 } 471 else if (serverType == "LDAP") 472 { 473 dbusObjectPath = ldapConfigObjectName; 474 } 475 476 BMCWEB_LOG_DEBUG("Remote Group={},LocalRole={}", *remoteGroup, 477 *localRole); 478 479 crow::connections::systemBus->async_method_call( 480 [asyncResp, serverType, localRole, 481 remoteGroup](const boost::system::error_code& ec) { 482 if (ec) 483 { 484 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 485 messages::internalError(asyncResp->res); 486 return; 487 } 488 nlohmann::json& remoteRoleJson = 489 asyncResp->res 490 .jsonValue[serverType]["RemoteRoleMapping"]; 491 nlohmann::json::object_t roleMapEntry; 492 roleMapEntry["LocalRole"] = *localRole; 493 roleMapEntry["RemoteGroup"] = *remoteGroup; 494 remoteRoleJson.emplace_back(std::move(roleMapEntry)); 495 }, 496 ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, 497 "Create", *remoteGroup, 498 getPrivilegeFromRoleId(std::move(*localRole))); 499 } 500 } 501 } 502 } 503 504 /** 505 * Function that retrieves all properties for LDAP config object 506 * into JSON 507 */ 508 template <typename CallbackFunc> 509 inline void getLDAPConfigData(const std::string& ldapType, 510 CallbackFunc&& callback) 511 { 512 constexpr std::array<std::string_view, 2> interfaces = { 513 ldapEnableInterface, ldapConfigInterface}; 514 515 dbus::utility::getDbusObject( 516 ldapConfigObjectName, interfaces, 517 [callback, ldapType](const boost::system::error_code& ec, 518 const dbus::utility::MapperGetObject& resp) { 519 if (ec || resp.empty()) 520 { 521 BMCWEB_LOG_WARNING( 522 "DBUS response error during getting of service name: {}", ec); 523 LDAPConfigData empty{}; 524 callback(false, empty, ldapType); 525 return; 526 } 527 std::string service = resp.begin()->first; 528 sdbusplus::message::object_path path(ldapRootObject); 529 dbus::utility::getManagedObjects( 530 service, path, 531 [callback, 532 ldapType](const boost::system::error_code& ec2, 533 const dbus::utility::ManagedObjectType& ldapObjects) { 534 LDAPConfigData confData{}; 535 if (ec2) 536 { 537 callback(false, confData, ldapType); 538 BMCWEB_LOG_WARNING("D-Bus responses error: {}", ec2); 539 return; 540 } 541 542 std::string ldapDbusType; 543 std::string searchString; 544 545 if (ldapType == "LDAP") 546 { 547 ldapDbusType = 548 "xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap"; 549 searchString = "openldap"; 550 } 551 else if (ldapType == "ActiveDirectory") 552 { 553 ldapDbusType = 554 "xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory"; 555 searchString = "active_directory"; 556 } 557 else 558 { 559 BMCWEB_LOG_ERROR("Can't get the DbusType for the given type={}", 560 ldapType); 561 callback(false, confData, ldapType); 562 return; 563 } 564 565 std::string ldapEnableInterfaceStr = ldapEnableInterface; 566 std::string ldapConfigInterfaceStr = ldapConfigInterface; 567 568 for (const auto& object : ldapObjects) 569 { 570 // let's find the object whose ldap type is equal to the 571 // given type 572 if (object.first.str.find(searchString) == std::string::npos) 573 { 574 continue; 575 } 576 577 for (const auto& interface : object.second) 578 { 579 if (interface.first == ldapEnableInterfaceStr) 580 { 581 // rest of the properties are string. 582 for (const auto& property : interface.second) 583 { 584 if (property.first == "Enabled") 585 { 586 const bool* value = 587 std::get_if<bool>(&property.second); 588 if (value == nullptr) 589 { 590 continue; 591 } 592 confData.serviceEnabled = *value; 593 break; 594 } 595 } 596 } 597 else if (interface.first == ldapConfigInterfaceStr) 598 { 599 for (const auto& property : interface.second) 600 { 601 const std::string* strValue = 602 std::get_if<std::string>(&property.second); 603 if (strValue == nullptr) 604 { 605 continue; 606 } 607 if (property.first == "LDAPServerURI") 608 { 609 confData.uri = *strValue; 610 } 611 else if (property.first == "LDAPBindDN") 612 { 613 confData.bindDN = *strValue; 614 } 615 else if (property.first == "LDAPBaseDN") 616 { 617 confData.baseDN = *strValue; 618 } 619 else if (property.first == "LDAPSearchScope") 620 { 621 confData.searchScope = *strValue; 622 } 623 else if (property.first == "GroupNameAttribute") 624 { 625 confData.groupAttribute = *strValue; 626 } 627 else if (property.first == "UserNameAttribute") 628 { 629 confData.userNameAttribute = *strValue; 630 } 631 else if (property.first == "LDAPType") 632 { 633 confData.serverType = *strValue; 634 } 635 } 636 } 637 else if (interface.first == 638 "xyz.openbmc_project.User.PrivilegeMapperEntry") 639 { 640 LDAPRoleMapData roleMapData{}; 641 for (const auto& property : interface.second) 642 { 643 const std::string* strValue = 644 std::get_if<std::string>(&property.second); 645 646 if (strValue == nullptr) 647 { 648 continue; 649 } 650 651 if (property.first == "GroupName") 652 { 653 roleMapData.groupName = *strValue; 654 } 655 else if (property.first == "Privilege") 656 { 657 roleMapData.privilege = *strValue; 658 } 659 } 660 661 confData.groupRoleList.emplace_back(object.first.str, 662 roleMapData); 663 } 664 } 665 } 666 callback(true, confData, ldapType); 667 }); 668 }); 669 } 670 671 /** 672 * @brief parses the authentication section under the LDAP 673 * @param input JSON data 674 * @param asyncResp pointer to the JSON response 675 * @param userName userName to be filled from the given JSON. 676 * @param password password to be filled from the given JSON. 677 */ 678 inline void parseLDAPAuthenticationJson( 679 nlohmann::json input, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 680 std::optional<std::string>& username, std::optional<std::string>& password) 681 { 682 std::optional<std::string> authType; 683 684 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 685 authType, "Username", username, "Password", 686 password)) 687 { 688 return; 689 } 690 if (!authType) 691 { 692 return; 693 } 694 if (*authType != "UsernameAndPassword") 695 { 696 messages::propertyValueNotInList(asyncResp->res, *authType, 697 "AuthenticationType"); 698 return; 699 } 700 } 701 /** 702 * @brief parses the LDAPService section under the LDAP 703 * @param input JSON data 704 * @param asyncResp pointer to the JSON response 705 * @param baseDNList baseDN to be filled from the given JSON. 706 * @param userNameAttribute userName to be filled from the given JSON. 707 * @param groupaAttribute password to be filled from the given JSON. 708 */ 709 710 inline void 711 parseLDAPServiceJson(nlohmann::json input, 712 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 713 std::optional<std::vector<std::string>>& baseDNList, 714 std::optional<std::string>& userNameAttribute, 715 std::optional<std::string>& groupsAttribute) 716 { 717 std::optional<nlohmann::json> searchSettings; 718 719 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 720 searchSettings)) 721 { 722 return; 723 } 724 if (!searchSettings) 725 { 726 return; 727 } 728 if (!json_util::readJson(*searchSettings, asyncResp->res, 729 "BaseDistinguishedNames", baseDNList, 730 "UsernameAttribute", userNameAttribute, 731 "GroupsAttribute", groupsAttribute)) 732 { 733 return; 734 } 735 } 736 /** 737 * @brief updates the LDAP server address and updates the 738 json response with the new value. 739 * @param serviceAddressList address to be updated. 740 * @param asyncResp pointer to the JSON response 741 * @param ldapServerElementName Type of LDAP 742 server(openLDAP/ActiveDirectory) 743 */ 744 745 inline void handleServiceAddressPatch( 746 const std::vector<std::string>& serviceAddressList, 747 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 748 const std::string& ldapServerElementName, 749 const std::string& ldapConfigObject) 750 { 751 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 752 ldapConfigInterface, "LDAPServerURI", 753 ldapServerElementName + "/ServiceAddress", 754 serviceAddressList.front()); 755 } 756 /** 757 * @brief updates the LDAP Bind DN and updates the 758 json response with the new value. 759 * @param username name of the user which needs to be updated. 760 * @param asyncResp pointer to the JSON response 761 * @param ldapServerElementName Type of LDAP 762 server(openLDAP/ActiveDirectory) 763 */ 764 765 inline void 766 handleUserNamePatch(const std::string& username, 767 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 768 const std::string& ldapServerElementName, 769 const std::string& ldapConfigObject) 770 { 771 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 772 ldapConfigInterface, "LDAPBindDN", 773 ldapServerElementName + "/Authentication/Username", 774 username); 775 } 776 777 /** 778 * @brief updates the LDAP password 779 * @param password : ldap password which needs to be updated. 780 * @param asyncResp pointer to the JSON response 781 * @param ldapServerElementName Type of LDAP 782 * server(openLDAP/ActiveDirectory) 783 */ 784 785 inline void 786 handlePasswordPatch(const std::string& password, 787 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 788 const std::string& ldapServerElementName, 789 const std::string& ldapConfigObject) 790 { 791 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 792 ldapConfigInterface, "LDAPBindDNPassword", 793 ldapServerElementName + "/Authentication/Password", 794 password); 795 } 796 797 /** 798 * @brief updates the LDAP BaseDN and updates the 799 json response with the new value. 800 * @param baseDNList baseDN list which needs to be updated. 801 * @param asyncResp pointer to the JSON response 802 * @param ldapServerElementName Type of LDAP 803 server(openLDAP/ActiveDirectory) 804 */ 805 806 inline void 807 handleBaseDNPatch(const std::vector<std::string>& baseDNList, 808 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 809 const std::string& ldapServerElementName, 810 const std::string& ldapConfigObject) 811 { 812 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 813 ldapConfigInterface, "LDAPBaseDN", 814 ldapServerElementName + 815 "/LDAPService/SearchSettings/BaseDistinguishedNames", 816 baseDNList.front()); 817 } 818 /** 819 * @brief updates the LDAP user name attribute and updates the 820 json response with the new value. 821 * @param userNameAttribute attribute to be updated. 822 * @param asyncResp pointer to the JSON response 823 * @param ldapServerElementName Type of LDAP 824 server(openLDAP/ActiveDirectory) 825 */ 826 827 inline void 828 handleUserNameAttrPatch(const std::string& userNameAttribute, 829 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 830 const std::string& ldapServerElementName, 831 const std::string& ldapConfigObject) 832 { 833 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 834 ldapConfigInterface, "UserNameAttribute", 835 ldapServerElementName + 836 "LDAPService/SearchSettings/UsernameAttribute", 837 userNameAttribute); 838 } 839 /** 840 * @brief updates the LDAP group attribute and updates the 841 json response with the new value. 842 * @param groupsAttribute attribute to be updated. 843 * @param asyncResp pointer to the JSON response 844 * @param ldapServerElementName Type of LDAP 845 server(openLDAP/ActiveDirectory) 846 */ 847 848 inline void handleGroupNameAttrPatch( 849 const std::string& groupsAttribute, 850 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 851 const std::string& ldapServerElementName, 852 const std::string& ldapConfigObject) 853 { 854 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 855 ldapConfigInterface, "GroupNameAttribute", 856 ldapServerElementName + 857 "/LDAPService/SearchSettings/GroupsAttribute", 858 groupsAttribute); 859 } 860 /** 861 * @brief updates the LDAP service enable and updates the 862 json response with the new value. 863 * @param input JSON data. 864 * @param asyncResp pointer to the JSON response 865 * @param ldapServerElementName Type of LDAP 866 server(openLDAP/ActiveDirectory) 867 */ 868 869 inline void handleServiceEnablePatch( 870 bool serviceEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 871 const std::string& ldapServerElementName, 872 const std::string& ldapConfigObject) 873 { 874 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 875 ldapEnableInterface, "Enabled", 876 ldapServerElementName + "/ServiceEnabled", serviceEnabled); 877 } 878 879 inline void 880 handleAuthMethodsPatch(nlohmann::json& input, 881 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 882 { 883 std::optional<bool> basicAuth; 884 std::optional<bool> cookie; 885 std::optional<bool> sessionToken; 886 std::optional<bool> xToken; 887 std::optional<bool> tls; 888 889 if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth, 890 "Cookie", cookie, "SessionToken", sessionToken, 891 "XToken", xToken, "TLS", tls)) 892 { 893 BMCWEB_LOG_ERROR("Cannot read values from AuthMethod tag"); 894 return; 895 } 896 897 // Make a copy of methods configuration 898 persistent_data::AuthConfigMethods authMethodsConfig = 899 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 900 901 if (basicAuth) 902 { 903 #ifndef BMCWEB_ENABLE_BASIC_AUTHENTICATION 904 messages::actionNotSupported( 905 asyncResp->res, 906 "Setting BasicAuth when basic-auth feature is disabled"); 907 return; 908 #endif 909 authMethodsConfig.basic = *basicAuth; 910 } 911 912 if (cookie) 913 { 914 #ifndef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 915 messages::actionNotSupported( 916 asyncResp->res, 917 "Setting Cookie when cookie-auth feature is disabled"); 918 return; 919 #endif 920 authMethodsConfig.cookie = *cookie; 921 } 922 923 if (sessionToken) 924 { 925 #ifndef BMCWEB_ENABLE_SESSION_AUTHENTICATION 926 messages::actionNotSupported( 927 asyncResp->res, 928 "Setting SessionToken when session-auth feature is disabled"); 929 return; 930 #endif 931 authMethodsConfig.sessionToken = *sessionToken; 932 } 933 934 if (xToken) 935 { 936 #ifndef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 937 messages::actionNotSupported( 938 asyncResp->res, 939 "Setting XToken when xtoken-auth feature is disabled"); 940 return; 941 #endif 942 authMethodsConfig.xtoken = *xToken; 943 } 944 945 if (tls) 946 { 947 #ifndef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 948 messages::actionNotSupported( 949 asyncResp->res, 950 "Setting TLS when mutual-tls-auth feature is disabled"); 951 return; 952 #endif 953 authMethodsConfig.tls = *tls; 954 } 955 956 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 957 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken && 958 !authMethodsConfig.tls) 959 { 960 // Do not allow user to disable everything 961 messages::actionNotSupported(asyncResp->res, 962 "of disabling all available methods"); 963 return; 964 } 965 966 persistent_data::SessionStore::getInstance().updateAuthMethodsConfig( 967 authMethodsConfig); 968 // Save configuration immediately 969 persistent_data::getConfig().writeData(); 970 971 messages::success(asyncResp->res); 972 } 973 974 /** 975 * @brief Get the required values from the given JSON, validates the 976 * value and create the LDAP config object. 977 * @param input JSON data 978 * @param asyncResp pointer to the JSON response 979 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 980 */ 981 982 inline void handleLDAPPatch(nlohmann::json& input, 983 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 984 const std::string& serverType) 985 { 986 std::string dbusObjectPath; 987 if (serverType == "ActiveDirectory") 988 { 989 dbusObjectPath = adConfigObject; 990 } 991 else if (serverType == "LDAP") 992 { 993 dbusObjectPath = ldapConfigObjectName; 994 } 995 else 996 { 997 return; 998 } 999 1000 std::optional<nlohmann::json> authentication; 1001 std::optional<nlohmann::json> ldapService; 1002 std::optional<std::vector<std::string>> serviceAddressList; 1003 std::optional<bool> serviceEnabled; 1004 std::optional<std::vector<std::string>> baseDNList; 1005 std::optional<std::string> userNameAttribute; 1006 std::optional<std::string> groupsAttribute; 1007 std::optional<std::string> userName; 1008 std::optional<std::string> password; 1009 std::optional<std::vector<nlohmann::json>> remoteRoleMapData; 1010 1011 if (!json_util::readJson(input, asyncResp->res, "Authentication", 1012 authentication, "LDAPService", ldapService, 1013 "ServiceAddresses", serviceAddressList, 1014 "ServiceEnabled", serviceEnabled, 1015 "RemoteRoleMapping", remoteRoleMapData)) 1016 { 1017 return; 1018 } 1019 1020 if (authentication) 1021 { 1022 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 1023 password); 1024 } 1025 if (ldapService) 1026 { 1027 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 1028 userNameAttribute, groupsAttribute); 1029 } 1030 if (serviceAddressList) 1031 { 1032 if (serviceAddressList->empty()) 1033 { 1034 messages::propertyValueNotInList( 1035 asyncResp->res, *serviceAddressList, "ServiceAddress"); 1036 return; 1037 } 1038 } 1039 if (baseDNList) 1040 { 1041 if (baseDNList->empty()) 1042 { 1043 messages::propertyValueNotInList(asyncResp->res, *baseDNList, 1044 "BaseDistinguishedNames"); 1045 return; 1046 } 1047 } 1048 1049 // nothing to update, then return 1050 if (!userName && !password && !serviceAddressList && !baseDNList && 1051 !userNameAttribute && !groupsAttribute && !serviceEnabled && 1052 !remoteRoleMapData) 1053 { 1054 return; 1055 } 1056 1057 // Get the existing resource first then keep modifying 1058 // whenever any property gets updated. 1059 getLDAPConfigData( 1060 serverType, 1061 [asyncResp, userName, password, baseDNList, userNameAttribute, 1062 groupsAttribute, serviceAddressList, serviceEnabled, dbusObjectPath, 1063 remoteRoleMapData](bool success, const LDAPConfigData& confData, 1064 const std::string& serverT) { 1065 if (!success) 1066 { 1067 messages::internalError(asyncResp->res); 1068 return; 1069 } 1070 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverT); 1071 if (confData.serviceEnabled) 1072 { 1073 // Disable the service first and update the rest of 1074 // the properties. 1075 handleServiceEnablePatch(false, asyncResp, serverT, dbusObjectPath); 1076 } 1077 1078 if (serviceAddressList) 1079 { 1080 handleServiceAddressPatch(*serviceAddressList, asyncResp, serverT, 1081 dbusObjectPath); 1082 } 1083 if (userName) 1084 { 1085 handleUserNamePatch(*userName, asyncResp, serverT, dbusObjectPath); 1086 } 1087 if (password) 1088 { 1089 handlePasswordPatch(*password, asyncResp, serverT, dbusObjectPath); 1090 } 1091 1092 if (baseDNList) 1093 { 1094 handleBaseDNPatch(*baseDNList, asyncResp, serverT, dbusObjectPath); 1095 } 1096 if (userNameAttribute) 1097 { 1098 handleUserNameAttrPatch(*userNameAttribute, asyncResp, serverT, 1099 dbusObjectPath); 1100 } 1101 if (groupsAttribute) 1102 { 1103 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, serverT, 1104 dbusObjectPath); 1105 } 1106 if (serviceEnabled) 1107 { 1108 // if user has given the value as true then enable 1109 // the service. if user has given false then no-op 1110 // as service is already stopped. 1111 if (*serviceEnabled) 1112 { 1113 handleServiceEnablePatch(*serviceEnabled, asyncResp, serverT, 1114 dbusObjectPath); 1115 } 1116 } 1117 else 1118 { 1119 // if user has not given the service enabled value 1120 // then revert it to the same state as it was 1121 // before. 1122 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1123 serverT, dbusObjectPath); 1124 } 1125 1126 if (remoteRoleMapData) 1127 { 1128 handleRoleMapPatch(asyncResp, confData.groupRoleList, serverT, 1129 *remoteRoleMapData); 1130 } 1131 }); 1132 } 1133 1134 inline void updateUserProperties( 1135 std::shared_ptr<bmcweb::AsyncResp> asyncResp, const std::string& username, 1136 const std::optional<std::string>& password, 1137 const std::optional<bool>& enabled, 1138 const std::optional<std::string>& roleId, const std::optional<bool>& locked, 1139 std::optional<std::vector<std::string>> accountTypes, bool userSelf) 1140 { 1141 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1142 tempObjPath /= username; 1143 std::string dbusObjectPath(tempObjPath); 1144 1145 dbus::utility::checkDbusPathExists( 1146 dbusObjectPath, [dbusObjectPath, username, password, roleId, enabled, 1147 locked, accountTypes(std::move(accountTypes)), 1148 userSelf, asyncResp{std::move(asyncResp)}](int rc) { 1149 if (rc <= 0) 1150 { 1151 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1152 username); 1153 return; 1154 } 1155 1156 if (password) 1157 { 1158 int retval = pamUpdatePassword(username, *password); 1159 1160 if (retval == PAM_USER_UNKNOWN) 1161 { 1162 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1163 username); 1164 } 1165 else if (retval == PAM_AUTHTOK_ERR) 1166 { 1167 // If password is invalid 1168 messages::propertyValueFormatError(asyncResp->res, nullptr, 1169 "Password"); 1170 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1171 } 1172 else if (retval != PAM_SUCCESS) 1173 { 1174 messages::internalError(asyncResp->res); 1175 return; 1176 } 1177 else 1178 { 1179 messages::success(asyncResp->res); 1180 } 1181 } 1182 1183 if (enabled) 1184 { 1185 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1186 dbusObjectPath, 1187 "xyz.openbmc_project.User.Attributes", 1188 "UserEnabled", "Enabled", *enabled); 1189 } 1190 1191 if (roleId) 1192 { 1193 std::string priv = getPrivilegeFromRoleId(*roleId); 1194 if (priv.empty()) 1195 { 1196 messages::propertyValueNotInList(asyncResp->res, true, 1197 "Locked"); 1198 return; 1199 } 1200 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1201 dbusObjectPath, 1202 "xyz.openbmc_project.User.Attributes", 1203 "UserPrivilege", "RoleId", priv); 1204 } 1205 1206 if (locked) 1207 { 1208 // admin can unlock the account which is locked by 1209 // successive authentication failures but admin should 1210 // not be allowed to lock an account. 1211 if (*locked) 1212 { 1213 messages::propertyValueNotInList(asyncResp->res, "true", 1214 "Locked"); 1215 return; 1216 } 1217 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1218 dbusObjectPath, 1219 "xyz.openbmc_project.User.Attributes", 1220 "UserLockedForFailedAttempt", "Locked", *locked); 1221 } 1222 1223 if (accountTypes) 1224 { 1225 patchAccountTypes(*accountTypes, asyncResp, dbusObjectPath, 1226 userSelf); 1227 } 1228 }); 1229 } 1230 1231 inline void handleAccountServiceHead( 1232 App& app, const crow::Request& req, 1233 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1234 { 1235 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1236 { 1237 return; 1238 } 1239 asyncResp->res.addHeader( 1240 boost::beast::http::field::link, 1241 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1242 } 1243 1244 inline void 1245 handleAccountServiceGet(App& app, const crow::Request& req, 1246 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1247 { 1248 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1249 { 1250 return; 1251 } 1252 1253 if (req.session == nullptr) 1254 { 1255 messages::internalError(asyncResp->res); 1256 return; 1257 } 1258 1259 asyncResp->res.addHeader( 1260 boost::beast::http::field::link, 1261 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1262 1263 const persistent_data::AuthConfigMethods& authMethodsConfig = 1264 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1265 1266 nlohmann::json& json = asyncResp->res.jsonValue; 1267 json["@odata.id"] = "/redfish/v1/AccountService"; 1268 json["@odata.type"] = "#AccountService." 1269 "v1_10_0.AccountService"; 1270 json["Id"] = "AccountService"; 1271 json["Name"] = "Account Service"; 1272 json["Description"] = "Account Service"; 1273 json["ServiceEnabled"] = true; 1274 json["MaxPasswordLength"] = 20; 1275 json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; 1276 json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; 1277 json["Oem"]["OpenBMC"]["@odata.type"] = 1278 "#OpenBMCAccountService.v1_0_0.AccountService"; 1279 json["Oem"]["OpenBMC"]["@odata.id"] = 1280 "/redfish/v1/AccountService#/Oem/OpenBMC"; 1281 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = 1282 authMethodsConfig.basic; 1283 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = 1284 authMethodsConfig.sessionToken; 1285 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; 1286 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; 1287 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; 1288 1289 // /redfish/v1/AccountService/LDAP/Certificates is something only 1290 // ConfigureManager can access then only display when the user has 1291 // permissions ConfigureManager 1292 Privileges effectiveUserPrivileges = 1293 redfish::getUserPrivileges(*req.session); 1294 1295 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 1296 effectiveUserPrivileges)) 1297 { 1298 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = 1299 "/redfish/v1/AccountService/LDAP/Certificates"; 1300 } 1301 sdbusplus::asio::getAllProperties( 1302 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1303 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.AccountPolicy", 1304 [asyncResp](const boost::system::error_code& ec, 1305 const dbus::utility::DBusPropertiesMap& propertiesList) { 1306 if (ec) 1307 { 1308 messages::internalError(asyncResp->res); 1309 return; 1310 } 1311 1312 BMCWEB_LOG_DEBUG("Got {}properties for AccountService", 1313 propertiesList.size()); 1314 1315 const uint8_t* minPasswordLength = nullptr; 1316 const uint32_t* accountUnlockTimeout = nullptr; 1317 const uint16_t* maxLoginAttemptBeforeLockout = nullptr; 1318 1319 const bool success = sdbusplus::unpackPropertiesNoThrow( 1320 dbus_utils::UnpackErrorPrinter(), propertiesList, 1321 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout", 1322 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout", 1323 maxLoginAttemptBeforeLockout); 1324 1325 if (!success) 1326 { 1327 messages::internalError(asyncResp->res); 1328 return; 1329 } 1330 1331 if (minPasswordLength != nullptr) 1332 { 1333 asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength; 1334 } 1335 1336 if (accountUnlockTimeout != nullptr) 1337 { 1338 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1339 *accountUnlockTimeout; 1340 } 1341 1342 if (maxLoginAttemptBeforeLockout != nullptr) 1343 { 1344 asyncResp->res.jsonValue["AccountLockoutThreshold"] = 1345 *maxLoginAttemptBeforeLockout; 1346 } 1347 }); 1348 1349 auto callback = [asyncResp](bool success, const LDAPConfigData& confData, 1350 const std::string& ldapType) { 1351 if (!success) 1352 { 1353 return; 1354 } 1355 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1356 }; 1357 1358 getLDAPConfigData("LDAP", callback); 1359 getLDAPConfigData("ActiveDirectory", callback); 1360 } 1361 1362 inline void handleAccountServicePatch( 1363 App& app, const crow::Request& req, 1364 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1365 { 1366 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1367 { 1368 return; 1369 } 1370 std::optional<uint32_t> unlockTimeout; 1371 std::optional<uint16_t> lockoutThreshold; 1372 std::optional<uint8_t> minPasswordLength; 1373 std::optional<uint16_t> maxPasswordLength; 1374 std::optional<nlohmann::json> ldapObject; 1375 std::optional<nlohmann::json> activeDirectoryObject; 1376 std::optional<nlohmann::json> oemObject; 1377 1378 if (!json_util::readJsonPatch( 1379 req, asyncResp->res, "AccountLockoutDuration", unlockTimeout, 1380 "AccountLockoutThreshold", lockoutThreshold, "MaxPasswordLength", 1381 maxPasswordLength, "MinPasswordLength", minPasswordLength, "LDAP", 1382 ldapObject, "ActiveDirectory", activeDirectoryObject, "Oem", 1383 oemObject)) 1384 { 1385 return; 1386 } 1387 1388 if (minPasswordLength) 1389 { 1390 setDbusProperty( 1391 asyncResp, "xyz.openbmc_project.User.Manager", 1392 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1393 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", 1394 "MinPasswordLength", *minPasswordLength); 1395 } 1396 1397 if (maxPasswordLength) 1398 { 1399 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1400 } 1401 1402 if (ldapObject) 1403 { 1404 handleLDAPPatch(*ldapObject, asyncResp, "LDAP"); 1405 } 1406 1407 if (std::optional<nlohmann::json> oemOpenBMCObject; 1408 oemObject && json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", 1409 oemOpenBMCObject)) 1410 { 1411 if (std::optional<nlohmann::json> authMethodsObject; 1412 oemOpenBMCObject && 1413 json_util::readJson(*oemOpenBMCObject, asyncResp->res, 1414 "AuthMethods", authMethodsObject)) 1415 { 1416 if (authMethodsObject) 1417 { 1418 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1419 } 1420 } 1421 } 1422 1423 if (activeDirectoryObject) 1424 { 1425 handleLDAPPatch(*activeDirectoryObject, asyncResp, "ActiveDirectory"); 1426 } 1427 1428 if (unlockTimeout) 1429 { 1430 setDbusProperty( 1431 asyncResp, "xyz.openbmc_project.User.Manager", 1432 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1433 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", 1434 "AccountLockoutDuration", *unlockTimeout); 1435 } 1436 if (lockoutThreshold) 1437 { 1438 setDbusProperty( 1439 asyncResp, "xyz.openbmc_project.User.Manager", 1440 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1441 "xyz.openbmc_project.User.AccountPolicy", 1442 "MaxLoginAttemptBeforeLockout", "AccountLockoutThreshold", 1443 *lockoutThreshold); 1444 } 1445 } 1446 1447 inline void handleAccountCollectionHead( 1448 App& app, const crow::Request& req, 1449 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1450 { 1451 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1452 { 1453 return; 1454 } 1455 asyncResp->res.addHeader( 1456 boost::beast::http::field::link, 1457 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1458 } 1459 1460 inline void handleAccountCollectionGet( 1461 App& app, const crow::Request& req, 1462 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1463 { 1464 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1465 { 1466 return; 1467 } 1468 1469 if (req.session == nullptr) 1470 { 1471 messages::internalError(asyncResp->res); 1472 return; 1473 } 1474 1475 asyncResp->res.addHeader( 1476 boost::beast::http::field::link, 1477 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1478 1479 asyncResp->res.jsonValue["@odata.id"] = 1480 "/redfish/v1/AccountService/Accounts"; 1481 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." 1482 "ManagerAccountCollection"; 1483 asyncResp->res.jsonValue["Name"] = "Accounts Collection"; 1484 asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; 1485 1486 Privileges effectiveUserPrivileges = 1487 redfish::getUserPrivileges(*req.session); 1488 1489 std::string thisUser; 1490 if (req.session) 1491 { 1492 thisUser = req.session->username; 1493 } 1494 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1495 dbus::utility::getManagedObjects( 1496 "xyz.openbmc_project.User.Manager", path, 1497 [asyncResp, thisUser, effectiveUserPrivileges]( 1498 const boost::system::error_code& ec, 1499 const dbus::utility::ManagedObjectType& users) { 1500 if (ec) 1501 { 1502 messages::internalError(asyncResp->res); 1503 return; 1504 } 1505 1506 bool userCanSeeAllAccounts = 1507 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); 1508 1509 bool userCanSeeSelf = 1510 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); 1511 1512 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 1513 memberArray = nlohmann::json::array(); 1514 1515 for (const auto& userpath : users) 1516 { 1517 std::string user = userpath.first.filename(); 1518 if (user.empty()) 1519 { 1520 messages::internalError(asyncResp->res); 1521 BMCWEB_LOG_ERROR("Invalid firmware ID"); 1522 1523 return; 1524 } 1525 1526 // As clarified by Redfish here: 1527 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1528 // Users without ConfigureUsers, only see their own 1529 // account. Users with ConfigureUsers, see all 1530 // accounts. 1531 if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf)) 1532 { 1533 nlohmann::json::object_t member; 1534 member["@odata.id"] = boost::urls::format( 1535 "/redfish/v1/AccountService/Accounts/{}", user); 1536 memberArray.emplace_back(std::move(member)); 1537 } 1538 } 1539 asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); 1540 }); 1541 } 1542 1543 inline void processAfterCreateUser( 1544 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1545 const std::string& username, const std::string& password, 1546 const boost::system::error_code& ec, sdbusplus::message_t& m) 1547 { 1548 if (ec) 1549 { 1550 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); 1551 return; 1552 } 1553 1554 if (pamUpdatePassword(username, password) != PAM_SUCCESS) 1555 { 1556 // At this point we have a user that's been 1557 // created, but the password set 1558 // failed.Something is wrong, so delete the user 1559 // that we've already created 1560 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1561 tempObjPath /= username; 1562 const std::string userPath(tempObjPath); 1563 1564 crow::connections::systemBus->async_method_call( 1565 [asyncResp, password](const boost::system::error_code& ec3) { 1566 if (ec3) 1567 { 1568 messages::internalError(asyncResp->res); 1569 return; 1570 } 1571 1572 // If password is invalid 1573 messages::propertyValueFormatError(asyncResp->res, nullptr, 1574 "Password"); 1575 }, 1576 "xyz.openbmc_project.User.Manager", userPath, 1577 "xyz.openbmc_project.Object.Delete", "Delete"); 1578 1579 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1580 return; 1581 } 1582 1583 messages::created(asyncResp->res); 1584 asyncResp->res.addHeader("Location", 1585 "/redfish/v1/AccountService/Accounts/" + username); 1586 } 1587 1588 inline void processAfterGetAllGroups( 1589 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1590 const std::string& username, const std::string& password, 1591 const std::string& roleId, bool enabled, 1592 std::optional<std::vector<std::string>> accountTypes, 1593 const std::vector<std::string>& allGroupsList) 1594 { 1595 std::vector<std::string> userGroups; 1596 std::vector<std::string> accountTypeUserGroups; 1597 1598 // If user specified account types then convert them to unix user groups 1599 if (accountTypes) 1600 { 1601 if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes, 1602 accountTypeUserGroups)) 1603 { 1604 // Problem in mapping Account Types to User Groups, Error already 1605 // logged. 1606 return; 1607 } 1608 } 1609 1610 for (const auto& grp : allGroupsList) 1611 { 1612 // If user specified the account type then only accept groups which are 1613 // in the account types group list. 1614 if (!accountTypeUserGroups.empty()) 1615 { 1616 bool found = false; 1617 for (const auto& grp1 : accountTypeUserGroups) 1618 { 1619 if (grp == grp1) 1620 { 1621 found = true; 1622 break; 1623 } 1624 } 1625 if (!found) 1626 { 1627 continue; 1628 } 1629 } 1630 1631 // Console access is provided to the user who is a member of 1632 // hostconsole group and has a administrator role. So, set 1633 // hostconsole group only for the administrator. 1634 if ((grp == "hostconsole") && (roleId != "priv-admin")) 1635 { 1636 if (!accountTypeUserGroups.empty()) 1637 { 1638 BMCWEB_LOG_ERROR( 1639 "Only administrator can get HostConsole access"); 1640 asyncResp->res.result(boost::beast::http::status::bad_request); 1641 return; 1642 } 1643 continue; 1644 } 1645 userGroups.emplace_back(grp); 1646 } 1647 1648 // Make sure user specified groups are valid. This is internal error because 1649 // it some inconsistencies between user manager and bmcweb. 1650 if (!accountTypeUserGroups.empty() && 1651 accountTypeUserGroups.size() != userGroups.size()) 1652 { 1653 messages::internalError(asyncResp->res); 1654 return; 1655 } 1656 crow::connections::systemBus->async_method_call( 1657 [asyncResp, username, password](const boost::system::error_code& ec2, 1658 sdbusplus::message_t& m) { 1659 processAfterCreateUser(asyncResp, username, password, ec2, m); 1660 }, 1661 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1662 "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups, 1663 roleId, enabled); 1664 } 1665 1666 inline void handleAccountCollectionPost( 1667 App& app, const crow::Request& req, 1668 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1669 { 1670 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1671 { 1672 return; 1673 } 1674 std::string username; 1675 std::string password; 1676 std::optional<std::string> roleIdJson; 1677 std::optional<bool> enabledJson; 1678 std::optional<std::vector<std::string>> accountTypes; 1679 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, 1680 "Password", password, "RoleId", roleIdJson, 1681 "Enabled", enabledJson, "AccountTypes", 1682 accountTypes)) 1683 { 1684 return; 1685 } 1686 1687 std::string roleId = roleIdJson.value_or("User"); 1688 std::string priv = getPrivilegeFromRoleId(roleId); 1689 if (priv.empty()) 1690 { 1691 messages::propertyValueNotInList(asyncResp->res, roleId, "RoleId"); 1692 return; 1693 } 1694 roleId = priv; 1695 1696 bool enabled = enabledJson.value_or(true); 1697 1698 // Reading AllGroups property 1699 sdbusplus::asio::getProperty<std::vector<std::string>>( 1700 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1701 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", 1702 "AllGroups", 1703 [asyncResp, username, password{std::move(password)}, roleId, enabled, 1704 accountTypes](const boost::system::error_code& ec, 1705 const std::vector<std::string>& allGroupsList) { 1706 if (ec) 1707 { 1708 BMCWEB_LOG_DEBUG("ERROR with async_method_call"); 1709 messages::internalError(asyncResp->res); 1710 return; 1711 } 1712 1713 if (allGroupsList.empty()) 1714 { 1715 messages::internalError(asyncResp->res); 1716 return; 1717 } 1718 1719 processAfterGetAllGroups(asyncResp, username, password, roleId, enabled, 1720 accountTypes, allGroupsList); 1721 }); 1722 } 1723 1724 inline void 1725 handleAccountHead(App& app, const crow::Request& req, 1726 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1727 const std::string& /*accountName*/) 1728 { 1729 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1730 { 1731 return; 1732 } 1733 asyncResp->res.addHeader( 1734 boost::beast::http::field::link, 1735 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1736 } 1737 1738 inline void 1739 handleAccountGet(App& app, const crow::Request& req, 1740 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1741 const std::string& accountName) 1742 { 1743 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1744 { 1745 return; 1746 } 1747 asyncResp->res.addHeader( 1748 boost::beast::http::field::link, 1749 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1750 1751 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1752 // If authentication is disabled, there are no user accounts 1753 messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName); 1754 return; 1755 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1756 1757 if (req.session == nullptr) 1758 { 1759 messages::internalError(asyncResp->res); 1760 return; 1761 } 1762 if (req.session->username != accountName) 1763 { 1764 // At this point we've determined that the user is trying to 1765 // modify a user that isn't them. We need to verify that they 1766 // have permissions to modify other users, so re-run the auth 1767 // check with the same permissions, minus ConfigureSelf. 1768 Privileges effectiveUserPrivileges = 1769 redfish::getUserPrivileges(*req.session); 1770 Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers", 1771 "ConfigureManager"}; 1772 if (!effectiveUserPrivileges.isSupersetOf( 1773 requiredPermissionsToChangeNonSelf)) 1774 { 1775 BMCWEB_LOG_DEBUG("GET Account denied access"); 1776 messages::insufficientPrivilege(asyncResp->res); 1777 return; 1778 } 1779 } 1780 1781 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1782 dbus::utility::getManagedObjects( 1783 "xyz.openbmc_project.User.Manager", path, 1784 [asyncResp, 1785 accountName](const boost::system::error_code& ec, 1786 const dbus::utility::ManagedObjectType& users) { 1787 if (ec) 1788 { 1789 messages::internalError(asyncResp->res); 1790 return; 1791 } 1792 const auto userIt = std::ranges::find_if( 1793 users, 1794 [accountName]( 1795 const std::pair<sdbusplus::message::object_path, 1796 dbus::utility::DBusInterfacesMap>& user) { 1797 return accountName == user.first.filename(); 1798 }); 1799 1800 if (userIt == users.end()) 1801 { 1802 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1803 accountName); 1804 return; 1805 } 1806 1807 asyncResp->res.jsonValue["@odata.type"] = 1808 "#ManagerAccount.v1_7_0.ManagerAccount"; 1809 asyncResp->res.jsonValue["Name"] = "User Account"; 1810 asyncResp->res.jsonValue["Description"] = "User Account"; 1811 asyncResp->res.jsonValue["Password"] = nullptr; 1812 asyncResp->res.jsonValue["StrictAccountTypes"] = true; 1813 1814 for (const auto& interface : userIt->second) 1815 { 1816 if (interface.first == "xyz.openbmc_project.User.Attributes") 1817 { 1818 for (const auto& property : interface.second) 1819 { 1820 if (property.first == "UserEnabled") 1821 { 1822 const bool* userEnabled = 1823 std::get_if<bool>(&property.second); 1824 if (userEnabled == nullptr) 1825 { 1826 BMCWEB_LOG_ERROR("UserEnabled wasn't a bool"); 1827 messages::internalError(asyncResp->res); 1828 return; 1829 } 1830 asyncResp->res.jsonValue["Enabled"] = *userEnabled; 1831 } 1832 else if (property.first == "UserLockedForFailedAttempt") 1833 { 1834 const bool* userLocked = 1835 std::get_if<bool>(&property.second); 1836 if (userLocked == nullptr) 1837 { 1838 BMCWEB_LOG_ERROR("UserLockedForF" 1839 "ailedAttempt " 1840 "wasn't a bool"); 1841 messages::internalError(asyncResp->res); 1842 return; 1843 } 1844 asyncResp->res.jsonValue["Locked"] = *userLocked; 1845 asyncResp->res 1846 .jsonValue["Locked@Redfish.AllowableValues"] = { 1847 "false"}; // can only unlock accounts 1848 } 1849 else if (property.first == "UserPrivilege") 1850 { 1851 const std::string* userPrivPtr = 1852 std::get_if<std::string>(&property.second); 1853 if (userPrivPtr == nullptr) 1854 { 1855 BMCWEB_LOG_ERROR("UserPrivilege wasn't a " 1856 "string"); 1857 messages::internalError(asyncResp->res); 1858 return; 1859 } 1860 std::string role = getRoleIdFromPrivilege(*userPrivPtr); 1861 if (role.empty()) 1862 { 1863 BMCWEB_LOG_ERROR("Invalid user role"); 1864 messages::internalError(asyncResp->res); 1865 return; 1866 } 1867 asyncResp->res.jsonValue["RoleId"] = role; 1868 1869 nlohmann::json& roleEntry = 1870 asyncResp->res.jsonValue["Links"]["Role"]; 1871 roleEntry["@odata.id"] = boost::urls::format( 1872 "/redfish/v1/AccountService/Roles/{}", role); 1873 } 1874 else if (property.first == "UserPasswordExpired") 1875 { 1876 const bool* userPasswordExpired = 1877 std::get_if<bool>(&property.second); 1878 if (userPasswordExpired == nullptr) 1879 { 1880 BMCWEB_LOG_ERROR( 1881 "UserPasswordExpired wasn't a bool"); 1882 messages::internalError(asyncResp->res); 1883 return; 1884 } 1885 asyncResp->res.jsonValue["PasswordChangeRequired"] = 1886 *userPasswordExpired; 1887 } 1888 else if (property.first == "UserGroups") 1889 { 1890 const std::vector<std::string>* userGroups = 1891 std::get_if<std::vector<std::string>>( 1892 &property.second); 1893 if (userGroups == nullptr) 1894 { 1895 BMCWEB_LOG_ERROR( 1896 "userGroups wasn't a string vector"); 1897 messages::internalError(asyncResp->res); 1898 return; 1899 } 1900 if (!translateUserGroup(*userGroups, asyncResp->res)) 1901 { 1902 BMCWEB_LOG_ERROR("userGroups mapping failed"); 1903 messages::internalError(asyncResp->res); 1904 return; 1905 } 1906 } 1907 } 1908 } 1909 } 1910 1911 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1912 "/redfish/v1/AccountService/Accounts/{}", accountName); 1913 asyncResp->res.jsonValue["Id"] = accountName; 1914 asyncResp->res.jsonValue["UserName"] = accountName; 1915 }); 1916 } 1917 1918 inline void 1919 handleAccountDelete(App& app, const crow::Request& req, 1920 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1921 const std::string& username) 1922 { 1923 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1924 { 1925 return; 1926 } 1927 1928 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1929 // If authentication is disabled, there are no user accounts 1930 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 1931 return; 1932 1933 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1934 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1935 tempObjPath /= username; 1936 const std::string userPath(tempObjPath); 1937 1938 crow::connections::systemBus->async_method_call( 1939 [asyncResp, username](const boost::system::error_code& ec) { 1940 if (ec) 1941 { 1942 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1943 username); 1944 return; 1945 } 1946 1947 messages::accountRemoved(asyncResp->res); 1948 }, 1949 "xyz.openbmc_project.User.Manager", userPath, 1950 "xyz.openbmc_project.Object.Delete", "Delete"); 1951 } 1952 1953 inline void 1954 handleAccountPatch(App& app, const crow::Request& req, 1955 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1956 const std::string& username) 1957 { 1958 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1959 { 1960 return; 1961 } 1962 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1963 // If authentication is disabled, there are no user accounts 1964 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 1965 return; 1966 1967 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1968 std::optional<std::string> newUserName; 1969 std::optional<std::string> password; 1970 std::optional<bool> enabled; 1971 std::optional<std::string> roleId; 1972 std::optional<bool> locked; 1973 std::optional<std::vector<std::string>> accountTypes; 1974 1975 bool userSelf = (username == req.session->username); 1976 1977 if (req.session == nullptr) 1978 { 1979 messages::internalError(asyncResp->res); 1980 return; 1981 } 1982 1983 Privileges effectiveUserPrivileges = 1984 redfish::getUserPrivileges(*req.session); 1985 Privileges configureUsers = {"ConfigureUsers"}; 1986 bool userHasConfigureUsers = 1987 effectiveUserPrivileges.isSupersetOf(configureUsers); 1988 if (userHasConfigureUsers) 1989 { 1990 // Users with ConfigureUsers can modify for all users 1991 if (!json_util::readJsonPatch( 1992 req, asyncResp->res, "UserName", newUserName, "Password", 1993 password, "RoleId", roleId, "Enabled", enabled, "Locked", 1994 locked, "AccountTypes", accountTypes)) 1995 { 1996 return; 1997 } 1998 } 1999 else 2000 { 2001 // ConfigureSelf accounts can only modify their own account 2002 if (!userSelf) 2003 { 2004 messages::insufficientPrivilege(asyncResp->res); 2005 return; 2006 } 2007 2008 // ConfigureSelf accounts can only modify their password 2009 if (!json_util::readJsonPatch(req, asyncResp->res, "Password", 2010 password)) 2011 { 2012 return; 2013 } 2014 } 2015 2016 // if user name is not provided in the patch method or if it 2017 // matches the user name in the URI, then we are treating it as 2018 // updating user properties other then username. If username 2019 // provided doesn't match the URI, then we are treating this as 2020 // user rename request. 2021 if (!newUserName || (newUserName.value() == username)) 2022 { 2023 updateUserProperties(asyncResp, username, password, enabled, roleId, 2024 locked, accountTypes, userSelf); 2025 return; 2026 } 2027 crow::connections::systemBus->async_method_call( 2028 [asyncResp, username, password(std::move(password)), 2029 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, 2030 locked, userSelf, accountTypes(std::move(accountTypes))]( 2031 const boost::system::error_code& ec, sdbusplus::message_t& m) { 2032 if (ec) 2033 { 2034 userErrorMessageHandler(m.get_error(), asyncResp, newUser, 2035 username); 2036 return; 2037 } 2038 2039 updateUserProperties(asyncResp, newUser, password, enabled, roleId, 2040 locked, accountTypes, userSelf); 2041 }, 2042 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 2043 "xyz.openbmc_project.User.Manager", "RenameUser", username, 2044 *newUserName); 2045 } 2046 2047 inline void requestAccountServiceRoutes(App& app) 2048 { 2049 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2050 .privileges(redfish::privileges::headAccountService) 2051 .methods(boost::beast::http::verb::head)( 2052 std::bind_front(handleAccountServiceHead, std::ref(app))); 2053 2054 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2055 .privileges(redfish::privileges::getAccountService) 2056 .methods(boost::beast::http::verb::get)( 2057 std::bind_front(handleAccountServiceGet, std::ref(app))); 2058 2059 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2060 .privileges(redfish::privileges::patchAccountService) 2061 .methods(boost::beast::http::verb::patch)( 2062 std::bind_front(handleAccountServicePatch, std::ref(app))); 2063 2064 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2065 .privileges(redfish::privileges::headManagerAccountCollection) 2066 .methods(boost::beast::http::verb::head)( 2067 std::bind_front(handleAccountCollectionHead, std::ref(app))); 2068 2069 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2070 .privileges(redfish::privileges::getManagerAccountCollection) 2071 .methods(boost::beast::http::verb::get)( 2072 std::bind_front(handleAccountCollectionGet, std::ref(app))); 2073 2074 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2075 .privileges(redfish::privileges::postManagerAccountCollection) 2076 .methods(boost::beast::http::verb::post)( 2077 std::bind_front(handleAccountCollectionPost, std::ref(app))); 2078 2079 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2080 .privileges(redfish::privileges::headManagerAccount) 2081 .methods(boost::beast::http::verb::head)( 2082 std::bind_front(handleAccountHead, std::ref(app))); 2083 2084 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2085 .privileges(redfish::privileges::getManagerAccount) 2086 .methods(boost::beast::http::verb::get)( 2087 std::bind_front(handleAccountGet, std::ref(app))); 2088 2089 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2090 // TODO this privilege should be using the generated endpoints, but 2091 // because of the special handling of ConfigureSelf, it's not able to 2092 // yet 2093 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) 2094 .methods(boost::beast::http::verb::patch)( 2095 std::bind_front(handleAccountPatch, std::ref(app))); 2096 2097 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2098 .privileges(redfish::privileges::deleteManagerAccount) 2099 .methods(boost::beast::http::verb::delete_)( 2100 std::bind_front(handleAccountDelete, std::ref(app))); 2101 } 2102 2103 } // namespace redfish 2104