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, 364 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input) 365 { 366 for (size_t index = 0; index < input.size(); index++) 367 { 368 std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson = 369 input[index]; 370 nlohmann::json::object_t* obj = 371 std::get_if<nlohmann::json::object_t>(&thisJson); 372 if (obj == nullptr) 373 { 374 // delete the existing object 375 if (index < roleMapObjData.size()) 376 { 377 crow::connections::systemBus->async_method_call( 378 [asyncResp, roleMapObjData, serverType, 379 index](const boost::system::error_code& ec) { 380 if (ec) 381 { 382 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 383 messages::internalError(asyncResp->res); 384 return; 385 } 386 asyncResp->res.jsonValue[serverType]["RemoteRoleMapping"] 387 [index] = nullptr; 388 }, 389 ldapDbusService, roleMapObjData[index].first, 390 "xyz.openbmc_project.Object.Delete", "Delete"); 391 } 392 else 393 { 394 BMCWEB_LOG_ERROR("Can't delete the object"); 395 messages::propertyValueTypeError(asyncResp->res, "null", 396 "RemoteRoleMapping/" + 397 std::to_string(index)); 398 return; 399 } 400 } 401 else if (obj->empty()) 402 { 403 // Don't do anything for the empty objects,parse next json 404 // eg {"RemoteRoleMapping",[{}]} 405 } 406 else 407 { 408 // update/create the object 409 std::optional<std::string> remoteGroup; 410 std::optional<std::string> localRole; 411 412 if (!json_util::readJsonObject(*obj, asyncResp->res, "RemoteGroup", 413 remoteGroup, "LocalRole", localRole)) 414 { 415 continue; 416 } 417 418 // Update existing RoleMapping Object 419 if (index < roleMapObjData.size()) 420 { 421 BMCWEB_LOG_DEBUG("Update Role Map Object"); 422 // If "RemoteGroup" info is provided 423 if (remoteGroup) 424 { 425 setDbusProperty( 426 asyncResp, ldapDbusService, roleMapObjData[index].first, 427 "xyz.openbmc_project.User.PrivilegeMapperEntry", 428 "GroupName", 429 std::format("RemoteRoleMapping/{}/RemoteGroup", index), 430 *remoteGroup); 431 } 432 433 // If "LocalRole" info is provided 434 if (localRole) 435 { 436 setDbusProperty( 437 asyncResp, ldapDbusService, roleMapObjData[index].first, 438 "xyz.openbmc_project.User.PrivilegeMapperEntry", 439 "Privilege", 440 std::format("RemoteRoleMapping/{}/LocalRole", index), 441 *localRole); 442 } 443 } 444 // Create a new RoleMapping Object. 445 else 446 { 447 BMCWEB_LOG_DEBUG( 448 "setRoleMappingProperties: Creating new Object"); 449 std::string pathString = "RemoteRoleMapping/" + 450 std::to_string(index); 451 452 if (!localRole) 453 { 454 messages::propertyMissing(asyncResp->res, 455 pathString + "/LocalRole"); 456 continue; 457 } 458 if (!remoteGroup) 459 { 460 messages::propertyMissing(asyncResp->res, 461 pathString + "/RemoteGroup"); 462 continue; 463 } 464 465 std::string dbusObjectPath; 466 if (serverType == "ActiveDirectory") 467 { 468 dbusObjectPath = adConfigObject; 469 } 470 else if (serverType == "LDAP") 471 { 472 dbusObjectPath = ldapConfigObjectName; 473 } 474 475 BMCWEB_LOG_DEBUG("Remote Group={},LocalRole={}", *remoteGroup, 476 *localRole); 477 478 crow::connections::systemBus->async_method_call( 479 [asyncResp, serverType, localRole, 480 remoteGroup](const boost::system::error_code& ec) { 481 if (ec) 482 { 483 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 484 messages::internalError(asyncResp->res); 485 return; 486 } 487 nlohmann::json& remoteRoleJson = 488 asyncResp->res 489 .jsonValue[serverType]["RemoteRoleMapping"]; 490 nlohmann::json::object_t roleMapEntry; 491 roleMapEntry["LocalRole"] = *localRole; 492 roleMapEntry["RemoteGroup"] = *remoteGroup; 493 remoteRoleJson.emplace_back(std::move(roleMapEntry)); 494 }, 495 ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, 496 "Create", *remoteGroup, 497 getPrivilegeFromRoleId(std::move(*localRole))); 498 } 499 } 500 } 501 } 502 503 /** 504 * Function that retrieves all properties for LDAP config object 505 * into JSON 506 */ 507 template <typename CallbackFunc> 508 inline void getLDAPConfigData(const std::string& ldapType, 509 CallbackFunc&& callback) 510 { 511 constexpr std::array<std::string_view, 2> interfaces = { 512 ldapEnableInterface, ldapConfigInterface}; 513 514 dbus::utility::getDbusObject( 515 ldapConfigObjectName, interfaces, 516 [callback = std::forward<CallbackFunc>(callback), 517 ldapType](const boost::system::error_code& ec, 518 const dbus::utility::MapperGetObject& resp) mutable { 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, ldapType]( 532 const boost::system::error_code& ec2, 533 const dbus::utility::ManagedObjectType& ldapObjects) mutable { 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 updates the LDAP server address and updates the 673 json response with the new value. 674 * @param serviceAddressList address to be updated. 675 * @param asyncResp pointer to the JSON response 676 * @param ldapServerElementName Type of LDAP 677 server(openLDAP/ActiveDirectory) 678 */ 679 680 inline void handleServiceAddressPatch( 681 const std::vector<std::string>& serviceAddressList, 682 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 683 const std::string& ldapServerElementName, 684 const std::string& ldapConfigObject) 685 { 686 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 687 ldapConfigInterface, "LDAPServerURI", 688 ldapServerElementName + "/ServiceAddress", 689 serviceAddressList.front()); 690 } 691 /** 692 * @brief updates the LDAP Bind DN and updates the 693 json response with the new value. 694 * @param username name of the user which needs to be updated. 695 * @param asyncResp pointer to the JSON response 696 * @param ldapServerElementName Type of LDAP 697 server(openLDAP/ActiveDirectory) 698 */ 699 700 inline void 701 handleUserNamePatch(const std::string& username, 702 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 703 const std::string& ldapServerElementName, 704 const std::string& ldapConfigObject) 705 { 706 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 707 ldapConfigInterface, "LDAPBindDN", 708 ldapServerElementName + "/Authentication/Username", 709 username); 710 } 711 712 /** 713 * @brief updates the LDAP password 714 * @param password : ldap password which needs to be updated. 715 * @param asyncResp pointer to the JSON response 716 * @param ldapServerElementName Type of LDAP 717 * server(openLDAP/ActiveDirectory) 718 */ 719 720 inline void 721 handlePasswordPatch(const std::string& password, 722 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 723 const std::string& ldapServerElementName, 724 const std::string& ldapConfigObject) 725 { 726 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 727 ldapConfigInterface, "LDAPBindDNPassword", 728 ldapServerElementName + "/Authentication/Password", 729 password); 730 } 731 732 /** 733 * @brief updates the LDAP BaseDN and updates the 734 json response with the new value. 735 * @param baseDNList baseDN list which needs to be updated. 736 * @param asyncResp pointer to the JSON response 737 * @param ldapServerElementName Type of LDAP 738 server(openLDAP/ActiveDirectory) 739 */ 740 741 inline void 742 handleBaseDNPatch(const std::vector<std::string>& baseDNList, 743 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 744 const std::string& ldapServerElementName, 745 const std::string& ldapConfigObject) 746 { 747 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 748 ldapConfigInterface, "LDAPBaseDN", 749 ldapServerElementName + 750 "/LDAPService/SearchSettings/BaseDistinguishedNames", 751 baseDNList.front()); 752 } 753 /** 754 * @brief updates the LDAP user name attribute and updates the 755 json response with the new value. 756 * @param userNameAttribute attribute to be updated. 757 * @param asyncResp pointer to the JSON response 758 * @param ldapServerElementName Type of LDAP 759 server(openLDAP/ActiveDirectory) 760 */ 761 762 inline void 763 handleUserNameAttrPatch(const std::string& userNameAttribute, 764 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 765 const std::string& ldapServerElementName, 766 const std::string& ldapConfigObject) 767 { 768 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 769 ldapConfigInterface, "UserNameAttribute", 770 ldapServerElementName + 771 "LDAPService/SearchSettings/UsernameAttribute", 772 userNameAttribute); 773 } 774 /** 775 * @brief updates the LDAP group attribute and updates the 776 json response with the new value. 777 * @param groupsAttribute attribute to be updated. 778 * @param asyncResp pointer to the JSON response 779 * @param ldapServerElementName Type of LDAP 780 server(openLDAP/ActiveDirectory) 781 */ 782 783 inline void handleGroupNameAttrPatch( 784 const std::string& groupsAttribute, 785 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 786 const std::string& ldapServerElementName, 787 const std::string& ldapConfigObject) 788 { 789 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 790 ldapConfigInterface, "GroupNameAttribute", 791 ldapServerElementName + 792 "/LDAPService/SearchSettings/GroupsAttribute", 793 groupsAttribute); 794 } 795 /** 796 * @brief updates the LDAP service enable and updates the 797 json response with the new value. 798 * @param input JSON data. 799 * @param asyncResp pointer to the JSON response 800 * @param ldapServerElementName Type of LDAP 801 server(openLDAP/ActiveDirectory) 802 */ 803 804 inline void handleServiceEnablePatch( 805 bool serviceEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 806 const std::string& ldapServerElementName, 807 const std::string& ldapConfigObject) 808 { 809 setDbusProperty(asyncResp, ldapDbusService, ldapConfigObject, 810 ldapEnableInterface, "Enabled", 811 ldapServerElementName + "/ServiceEnabled", serviceEnabled); 812 } 813 814 struct AuthMethods 815 { 816 std::optional<bool> basicAuth; 817 std::optional<bool> cookie; 818 std::optional<bool> sessionToken; 819 std::optional<bool> xToken; 820 std::optional<bool> tls; 821 }; 822 823 inline void 824 handleAuthMethodsPatch(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 825 const AuthMethods& auth) 826 { 827 persistent_data::AuthConfigMethods& authMethodsConfig = 828 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 829 830 if (auth.basicAuth) 831 { 832 #ifndef BMCWEB_ENABLE_BASIC_AUTHENTICATION 833 messages::actionNotSupported( 834 asyncResp->res, 835 "Setting BasicAuth when basic-auth feature is disabled"); 836 return; 837 #endif 838 authMethodsConfig.basic = *auth.basicAuth; 839 } 840 841 if (auth.cookie) 842 { 843 #ifndef BMCWEB_ENABLE_COOKIE_AUTHENTICATION 844 messages::actionNotSupported( 845 asyncResp->res, 846 "Setting Cookie when cookie-auth feature is disabled"); 847 return; 848 #endif 849 authMethodsConfig.cookie = *auth.cookie; 850 } 851 852 if (auth.sessionToken) 853 { 854 #ifndef BMCWEB_ENABLE_SESSION_AUTHENTICATION 855 messages::actionNotSupported( 856 asyncResp->res, 857 "Setting SessionToken when session-auth feature is disabled"); 858 return; 859 #endif 860 authMethodsConfig.sessionToken = *auth.sessionToken; 861 } 862 863 if (auth.xToken) 864 { 865 #ifndef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION 866 messages::actionNotSupported( 867 asyncResp->res, 868 "Setting XToken when xtoken-auth feature is disabled"); 869 return; 870 #endif 871 authMethodsConfig.xtoken = *auth.xToken; 872 } 873 874 if (auth.tls) 875 { 876 #ifndef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 877 messages::actionNotSupported( 878 asyncResp->res, 879 "Setting TLS when mutual-tls-auth feature is disabled"); 880 return; 881 #endif 882 authMethodsConfig.tls = *auth.tls; 883 } 884 885 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 886 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken && 887 !authMethodsConfig.tls) 888 { 889 // Do not allow user to disable everything 890 messages::actionNotSupported(asyncResp->res, 891 "of disabling all available methods"); 892 return; 893 } 894 895 persistent_data::SessionStore::getInstance().updateAuthMethodsConfig( 896 authMethodsConfig); 897 // Save configuration immediately 898 persistent_data::getConfig().writeData(); 899 900 messages::success(asyncResp->res); 901 } 902 903 /** 904 * @brief Get the required values from the given JSON, validates the 905 * value and create the LDAP config object. 906 * @param input JSON data 907 * @param asyncResp pointer to the JSON response 908 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 909 */ 910 911 struct LdapPatchParams 912 { 913 std::optional<std::string> authType; 914 std::optional<std::vector<std::string>> serviceAddressList; 915 std::optional<bool> serviceEnabled; 916 std::optional<std::vector<std::string>> baseDNList; 917 std::optional<std::string> userNameAttribute; 918 std::optional<std::string> groupsAttribute; 919 std::optional<std::string> userName; 920 std::optional<std::string> password; 921 std::optional< 922 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>> 923 remoteRoleMapData; 924 }; 925 926 inline void handleLDAPPatch(LdapPatchParams&& input, 927 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 928 const std::string& serverType) 929 { 930 std::string dbusObjectPath; 931 if (serverType == "ActiveDirectory") 932 { 933 dbusObjectPath = adConfigObject; 934 } 935 else if (serverType == "LDAP") 936 { 937 dbusObjectPath = ldapConfigObjectName; 938 } 939 else 940 { 941 BMCWEB_LOG_ERROR("serverType wasn't AD or LDAP but was {}????", 942 serverType); 943 return; 944 } 945 946 if (input.authType && *input.authType != "UsernameAndPassword") 947 { 948 messages::propertyValueNotInList(asyncResp->res, *input.authType, 949 "AuthenticationType"); 950 return; 951 } 952 953 if (input.serviceAddressList) 954 { 955 if (input.serviceAddressList->empty()) 956 { 957 messages::propertyValueNotInList( 958 asyncResp->res, *input.serviceAddressList, "ServiceAddress"); 959 return; 960 } 961 } 962 if (input.baseDNList) 963 { 964 if (input.baseDNList->empty()) 965 { 966 messages::propertyValueNotInList(asyncResp->res, *input.baseDNList, 967 "BaseDistinguishedNames"); 968 return; 969 } 970 } 971 972 // nothing to update, then return 973 if (!input.userName && !input.password && !input.serviceAddressList && 974 !input.baseDNList && !input.userNameAttribute && 975 !input.groupsAttribute && !input.serviceEnabled && 976 !input.remoteRoleMapData) 977 { 978 return; 979 } 980 981 // Get the existing resource first then keep modifying 982 // whenever any property gets updated. 983 getLDAPConfigData(serverType, 984 [asyncResp, input = std::move(input), 985 dbusObjectPath = std::move(dbusObjectPath)]( 986 bool success, const LDAPConfigData& confData, 987 const std::string& serverT) mutable { 988 if (!success) 989 { 990 messages::internalError(asyncResp->res); 991 return; 992 } 993 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverT); 994 if (confData.serviceEnabled) 995 { 996 // Disable the service first and update the rest of 997 // the properties. 998 handleServiceEnablePatch(false, asyncResp, serverT, dbusObjectPath); 999 } 1000 1001 if (input.serviceAddressList) 1002 { 1003 handleServiceAddressPatch(*input.serviceAddressList, asyncResp, 1004 serverT, dbusObjectPath); 1005 } 1006 if (input.userName) 1007 { 1008 handleUserNamePatch(*input.userName, asyncResp, serverT, 1009 dbusObjectPath); 1010 } 1011 if (input.password) 1012 { 1013 handlePasswordPatch(*input.password, asyncResp, serverT, 1014 dbusObjectPath); 1015 } 1016 1017 if (input.baseDNList) 1018 { 1019 handleBaseDNPatch(*input.baseDNList, asyncResp, serverT, 1020 dbusObjectPath); 1021 } 1022 if (input.userNameAttribute) 1023 { 1024 handleUserNameAttrPatch(*input.userNameAttribute, asyncResp, 1025 serverT, dbusObjectPath); 1026 } 1027 if (input.groupsAttribute) 1028 { 1029 handleGroupNameAttrPatch(*input.groupsAttribute, asyncResp, serverT, 1030 dbusObjectPath); 1031 } 1032 if (input.serviceEnabled) 1033 { 1034 // if user has given the value as true then enable 1035 // the service. if user has given false then no-op 1036 // as service is already stopped. 1037 if (*input.serviceEnabled) 1038 { 1039 handleServiceEnablePatch(*input.serviceEnabled, asyncResp, 1040 serverT, dbusObjectPath); 1041 } 1042 } 1043 else 1044 { 1045 // if user has not given the service enabled value 1046 // then revert it to the same state as it was 1047 // before. 1048 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1049 serverT, dbusObjectPath); 1050 } 1051 1052 if (input.remoteRoleMapData) 1053 { 1054 handleRoleMapPatch(asyncResp, confData.groupRoleList, serverT, 1055 *input.remoteRoleMapData); 1056 } 1057 }); 1058 } 1059 1060 inline void updateUserProperties( 1061 std::shared_ptr<bmcweb::AsyncResp> asyncResp, const std::string& username, 1062 const std::optional<std::string>& password, 1063 const std::optional<bool>& enabled, 1064 const std::optional<std::string>& roleId, const std::optional<bool>& locked, 1065 std::optional<std::vector<std::string>> accountTypes, bool userSelf) 1066 { 1067 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1068 tempObjPath /= username; 1069 std::string dbusObjectPath(tempObjPath); 1070 1071 dbus::utility::checkDbusPathExists( 1072 dbusObjectPath, [dbusObjectPath, username, password, roleId, enabled, 1073 locked, accountTypes(std::move(accountTypes)), 1074 userSelf, asyncResp{std::move(asyncResp)}](int rc) { 1075 if (rc <= 0) 1076 { 1077 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1078 username); 1079 return; 1080 } 1081 1082 if (password) 1083 { 1084 int retval = pamUpdatePassword(username, *password); 1085 1086 if (retval == PAM_USER_UNKNOWN) 1087 { 1088 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1089 username); 1090 } 1091 else if (retval == PAM_AUTHTOK_ERR) 1092 { 1093 // If password is invalid 1094 messages::propertyValueFormatError(asyncResp->res, nullptr, 1095 "Password"); 1096 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1097 } 1098 else if (retval != PAM_SUCCESS) 1099 { 1100 messages::internalError(asyncResp->res); 1101 return; 1102 } 1103 else 1104 { 1105 messages::success(asyncResp->res); 1106 } 1107 } 1108 1109 if (enabled) 1110 { 1111 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1112 dbusObjectPath, 1113 "xyz.openbmc_project.User.Attributes", 1114 "UserEnabled", "Enabled", *enabled); 1115 } 1116 1117 if (roleId) 1118 { 1119 std::string priv = getPrivilegeFromRoleId(*roleId); 1120 if (priv.empty()) 1121 { 1122 messages::propertyValueNotInList(asyncResp->res, true, 1123 "Locked"); 1124 return; 1125 } 1126 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1127 dbusObjectPath, 1128 "xyz.openbmc_project.User.Attributes", 1129 "UserPrivilege", "RoleId", priv); 1130 } 1131 1132 if (locked) 1133 { 1134 // admin can unlock the account which is locked by 1135 // successive authentication failures but admin should 1136 // not be allowed to lock an account. 1137 if (*locked) 1138 { 1139 messages::propertyValueNotInList(asyncResp->res, "true", 1140 "Locked"); 1141 return; 1142 } 1143 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1144 dbusObjectPath, 1145 "xyz.openbmc_project.User.Attributes", 1146 "UserLockedForFailedAttempt", "Locked", *locked); 1147 } 1148 1149 if (accountTypes) 1150 { 1151 patchAccountTypes(*accountTypes, asyncResp, dbusObjectPath, 1152 userSelf); 1153 } 1154 }); 1155 } 1156 1157 inline void handleAccountServiceHead( 1158 App& app, const crow::Request& req, 1159 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1160 { 1161 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1162 { 1163 return; 1164 } 1165 asyncResp->res.addHeader( 1166 boost::beast::http::field::link, 1167 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1168 } 1169 1170 inline void 1171 handleAccountServiceGet(App& app, const crow::Request& req, 1172 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1173 { 1174 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1175 { 1176 return; 1177 } 1178 1179 if (req.session == nullptr) 1180 { 1181 messages::internalError(asyncResp->res); 1182 return; 1183 } 1184 1185 const persistent_data::AuthConfigMethods& authMethodsConfig = 1186 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1187 1188 asyncResp->res.addHeader( 1189 boost::beast::http::field::link, 1190 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1191 1192 nlohmann::json& json = asyncResp->res.jsonValue; 1193 json["@odata.id"] = "/redfish/v1/AccountService"; 1194 json["@odata.type"] = "#AccountService.v1_15_0.AccountService"; 1195 json["Id"] = "AccountService"; 1196 json["Name"] = "Account Service"; 1197 json["Description"] = "Account Service"; 1198 json["ServiceEnabled"] = true; 1199 json["MaxPasswordLength"] = 20; 1200 json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; 1201 json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; 1202 json["HTTPBasicAuth"] = authMethodsConfig.basic 1203 ? account_service::BasicAuthState::Enabled 1204 : account_service::BasicAuthState::Disabled; 1205 1206 nlohmann::json::array_t allowed; 1207 allowed.emplace_back(account_service::BasicAuthState::Enabled); 1208 allowed.emplace_back(account_service::BasicAuthState::Disabled); 1209 json["HTTPBasicAuth@AllowableValues"] = std::move(allowed); 1210 1211 json["Oem"]["OpenBMC"]["@odata.type"] = 1212 "#OpenBMCAccountService.v1_0_0.AccountService"; 1213 json["Oem"]["OpenBMC"]["@odata.id"] = 1214 "/redfish/v1/AccountService#/Oem/OpenBMC"; 1215 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = 1216 authMethodsConfig.basic; 1217 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = 1218 authMethodsConfig.sessionToken; 1219 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; 1220 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; 1221 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; 1222 1223 // /redfish/v1/AccountService/LDAP/Certificates is something only 1224 // ConfigureManager can access then only display when the user has 1225 // permissions ConfigureManager 1226 Privileges effectiveUserPrivileges = 1227 redfish::getUserPrivileges(*req.session); 1228 1229 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 1230 effectiveUserPrivileges)) 1231 { 1232 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = 1233 "/redfish/v1/AccountService/LDAP/Certificates"; 1234 } 1235 sdbusplus::asio::getAllProperties( 1236 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1237 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.AccountPolicy", 1238 [asyncResp](const boost::system::error_code& ec, 1239 const dbus::utility::DBusPropertiesMap& propertiesList) { 1240 if (ec) 1241 { 1242 messages::internalError(asyncResp->res); 1243 return; 1244 } 1245 1246 BMCWEB_LOG_DEBUG("Got {}properties for AccountService", 1247 propertiesList.size()); 1248 1249 const uint8_t* minPasswordLength = nullptr; 1250 const uint32_t* accountUnlockTimeout = nullptr; 1251 const uint16_t* maxLoginAttemptBeforeLockout = nullptr; 1252 1253 const bool success = sdbusplus::unpackPropertiesNoThrow( 1254 dbus_utils::UnpackErrorPrinter(), propertiesList, 1255 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout", 1256 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout", 1257 maxLoginAttemptBeforeLockout); 1258 1259 if (!success) 1260 { 1261 messages::internalError(asyncResp->res); 1262 return; 1263 } 1264 1265 if (minPasswordLength != nullptr) 1266 { 1267 asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength; 1268 } 1269 1270 if (accountUnlockTimeout != nullptr) 1271 { 1272 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1273 *accountUnlockTimeout; 1274 } 1275 1276 if (maxLoginAttemptBeforeLockout != nullptr) 1277 { 1278 asyncResp->res.jsonValue["AccountLockoutThreshold"] = 1279 *maxLoginAttemptBeforeLockout; 1280 } 1281 }); 1282 1283 auto callback = [asyncResp](bool success, const LDAPConfigData& confData, 1284 const std::string& ldapType) { 1285 if (!success) 1286 { 1287 return; 1288 } 1289 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1290 }; 1291 1292 getLDAPConfigData("LDAP", callback); 1293 getLDAPConfigData("ActiveDirectory", callback); 1294 } 1295 1296 inline void handleAccountServicePatch( 1297 App& app, const crow::Request& req, 1298 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1299 { 1300 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1301 { 1302 return; 1303 } 1304 std::optional<uint32_t> unlockTimeout; 1305 std::optional<uint16_t> lockoutThreshold; 1306 std::optional<uint8_t> minPasswordLength; 1307 std::optional<uint16_t> maxPasswordLength; 1308 LdapPatchParams ldapObject; 1309 LdapPatchParams activeDirectoryObject; 1310 AuthMethods auth; 1311 std::optional<std::string> httpBasicAuth; 1312 // clang-format off 1313 if (!json_util::readJsonPatch( 1314 req, asyncResp->res, 1315 "AccountLockoutDuration", unlockTimeout, 1316 "AccountLockoutThreshold", lockoutThreshold, 1317 "ActiveDirectory/Authentication/AuthenticationType", activeDirectoryObject.authType, 1318 "ActiveDirectory/Authentication/Password", activeDirectoryObject.password, 1319 "ActiveDirectory/Authentication/Username", activeDirectoryObject.userName, 1320 "ActiveDirectory/LDAPService/SearchSettings/BaseDistinguishedNames", activeDirectoryObject.baseDNList, 1321 "ActiveDirectory/LDAPService/SearchSettings/GroupsAttribute", activeDirectoryObject.groupsAttribute, 1322 "ActiveDirectory/LDAPService/SearchSettings/UsernameAttribute", activeDirectoryObject.userNameAttribute, 1323 "ActiveDirectory/RemoteRoleMapping", activeDirectoryObject.remoteRoleMapData, 1324 "ActiveDirectory/ServiceAddresses", activeDirectoryObject.serviceAddressList, 1325 "ActiveDirectory/ServiceEnabled", activeDirectoryObject.serviceEnabled, 1326 "LDAP/Authentication/AuthenticationType", ldapObject.authType, 1327 "LDAP/Authentication/Password", ldapObject.password, 1328 "LDAP/Authentication/Username", ldapObject.userName, 1329 "LDAP/LDAPService/SearchSettings/BaseDistinguishedNames", ldapObject.baseDNList, 1330 "LDAP/LDAPService/SearchSettings/GroupsAttribute", ldapObject.groupsAttribute, 1331 "LDAP/LDAPService/SearchSettings/UsernameAttribute", ldapObject.userNameAttribute, 1332 "LDAP/RemoteRoleMapping", ldapObject.remoteRoleMapData, 1333 "LDAP/ServiceAddresses", ldapObject.serviceAddressList, 1334 "LDAP/ServiceEnabled", ldapObject.serviceEnabled, 1335 "MaxPasswordLength", maxPasswordLength, 1336 "MinPasswordLength", minPasswordLength, 1337 "Oem/OpenBMC/AuthMethods/BasicAuth", auth.basicAuth, 1338 "Oem/OpenBMC/AuthMethods/Cookie", auth.cookie, 1339 "Oem/OpenBMC/AuthMethods/SessionToken", auth.sessionToken, 1340 "Oem/OpenBMC/AuthMethods/TLS", auth.tls, 1341 "Oem/OpenBMC/AuthMethods/XToken", auth.xToken, 1342 "HTTPBasicAuth", httpBasicAuth)) 1343 { 1344 return; 1345 } 1346 // clang-format on 1347 1348 if (httpBasicAuth) 1349 { 1350 if (*httpBasicAuth == "Enabled") 1351 { 1352 auth.basicAuth = true; 1353 } 1354 else if (*httpBasicAuth == "Disabled") 1355 { 1356 auth.basicAuth = false; 1357 } 1358 else 1359 { 1360 messages::propertyValueNotInList(asyncResp->res, "HttpBasicAuth", 1361 *httpBasicAuth); 1362 } 1363 } 1364 1365 if (minPasswordLength) 1366 { 1367 setDbusProperty( 1368 asyncResp, "xyz.openbmc_project.User.Manager", 1369 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1370 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", 1371 "MinPasswordLength", *minPasswordLength); 1372 } 1373 1374 if (maxPasswordLength) 1375 { 1376 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1377 } 1378 1379 handleLDAPPatch(std::move(activeDirectoryObject), asyncResp, 1380 "ActiveDirectory"); 1381 handleLDAPPatch(std::move(ldapObject), asyncResp, "LDAP"); 1382 1383 handleAuthMethodsPatch(asyncResp, auth); 1384 1385 if (unlockTimeout) 1386 { 1387 setDbusProperty( 1388 asyncResp, "xyz.openbmc_project.User.Manager", 1389 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1390 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", 1391 "AccountLockoutDuration", *unlockTimeout); 1392 } 1393 if (lockoutThreshold) 1394 { 1395 setDbusProperty( 1396 asyncResp, "xyz.openbmc_project.User.Manager", 1397 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1398 "xyz.openbmc_project.User.AccountPolicy", 1399 "MaxLoginAttemptBeforeLockout", "AccountLockoutThreshold", 1400 *lockoutThreshold); 1401 } 1402 } 1403 1404 inline void handleAccountCollectionHead( 1405 App& app, const crow::Request& req, 1406 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1407 { 1408 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1409 { 1410 return; 1411 } 1412 asyncResp->res.addHeader( 1413 boost::beast::http::field::link, 1414 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1415 } 1416 1417 inline void handleAccountCollectionGet( 1418 App& app, const crow::Request& req, 1419 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1420 { 1421 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1422 { 1423 return; 1424 } 1425 1426 if (req.session == nullptr) 1427 { 1428 messages::internalError(asyncResp->res); 1429 return; 1430 } 1431 1432 asyncResp->res.addHeader( 1433 boost::beast::http::field::link, 1434 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1435 1436 asyncResp->res.jsonValue["@odata.id"] = 1437 "/redfish/v1/AccountService/Accounts"; 1438 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." 1439 "ManagerAccountCollection"; 1440 asyncResp->res.jsonValue["Name"] = "Accounts Collection"; 1441 asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; 1442 1443 Privileges effectiveUserPrivileges = 1444 redfish::getUserPrivileges(*req.session); 1445 1446 std::string thisUser; 1447 if (req.session) 1448 { 1449 thisUser = req.session->username; 1450 } 1451 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1452 dbus::utility::getManagedObjects( 1453 "xyz.openbmc_project.User.Manager", path, 1454 [asyncResp, thisUser, effectiveUserPrivileges]( 1455 const boost::system::error_code& ec, 1456 const dbus::utility::ManagedObjectType& users) { 1457 if (ec) 1458 { 1459 messages::internalError(asyncResp->res); 1460 return; 1461 } 1462 1463 bool userCanSeeAllAccounts = 1464 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); 1465 1466 bool userCanSeeSelf = 1467 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); 1468 1469 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 1470 memberArray = nlohmann::json::array(); 1471 1472 for (const auto& userpath : users) 1473 { 1474 std::string user = userpath.first.filename(); 1475 if (user.empty()) 1476 { 1477 messages::internalError(asyncResp->res); 1478 BMCWEB_LOG_ERROR("Invalid firmware ID"); 1479 1480 return; 1481 } 1482 1483 // As clarified by Redfish here: 1484 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1485 // Users without ConfigureUsers, only see their own 1486 // account. Users with ConfigureUsers, see all 1487 // accounts. 1488 if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf)) 1489 { 1490 nlohmann::json::object_t member; 1491 member["@odata.id"] = boost::urls::format( 1492 "/redfish/v1/AccountService/Accounts/{}", user); 1493 memberArray.emplace_back(std::move(member)); 1494 } 1495 } 1496 asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); 1497 }); 1498 } 1499 1500 inline void processAfterCreateUser( 1501 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1502 const std::string& username, const std::string& password, 1503 const boost::system::error_code& ec, sdbusplus::message_t& m) 1504 { 1505 if (ec) 1506 { 1507 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); 1508 return; 1509 } 1510 1511 if (pamUpdatePassword(username, password) != PAM_SUCCESS) 1512 { 1513 // At this point we have a user that's been 1514 // created, but the password set 1515 // failed.Something is wrong, so delete the user 1516 // that we've already created 1517 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1518 tempObjPath /= username; 1519 const std::string userPath(tempObjPath); 1520 1521 crow::connections::systemBus->async_method_call( 1522 [asyncResp, password](const boost::system::error_code& ec3) { 1523 if (ec3) 1524 { 1525 messages::internalError(asyncResp->res); 1526 return; 1527 } 1528 1529 // If password is invalid 1530 messages::propertyValueFormatError(asyncResp->res, nullptr, 1531 "Password"); 1532 }, 1533 "xyz.openbmc_project.User.Manager", userPath, 1534 "xyz.openbmc_project.Object.Delete", "Delete"); 1535 1536 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1537 return; 1538 } 1539 1540 messages::created(asyncResp->res); 1541 asyncResp->res.addHeader("Location", 1542 "/redfish/v1/AccountService/Accounts/" + username); 1543 } 1544 1545 inline void processAfterGetAllGroups( 1546 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1547 const std::string& username, const std::string& password, 1548 const std::string& roleId, bool enabled, 1549 std::optional<std::vector<std::string>> accountTypes, 1550 const std::vector<std::string>& allGroupsList) 1551 { 1552 std::vector<std::string> userGroups; 1553 std::vector<std::string> accountTypeUserGroups; 1554 1555 // If user specified account types then convert them to unix user groups 1556 if (accountTypes) 1557 { 1558 if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes, 1559 accountTypeUserGroups)) 1560 { 1561 // Problem in mapping Account Types to User Groups, Error already 1562 // logged. 1563 return; 1564 } 1565 } 1566 1567 for (const auto& grp : allGroupsList) 1568 { 1569 // If user specified the account type then only accept groups which are 1570 // in the account types group list. 1571 if (!accountTypeUserGroups.empty()) 1572 { 1573 bool found = false; 1574 for (const auto& grp1 : accountTypeUserGroups) 1575 { 1576 if (grp == grp1) 1577 { 1578 found = true; 1579 break; 1580 } 1581 } 1582 if (!found) 1583 { 1584 continue; 1585 } 1586 } 1587 1588 // Console access is provided to the user who is a member of 1589 // hostconsole group and has a administrator role. So, set 1590 // hostconsole group only for the administrator. 1591 if ((grp == "hostconsole") && (roleId != "priv-admin")) 1592 { 1593 if (!accountTypeUserGroups.empty()) 1594 { 1595 BMCWEB_LOG_ERROR( 1596 "Only administrator can get HostConsole access"); 1597 asyncResp->res.result(boost::beast::http::status::bad_request); 1598 return; 1599 } 1600 continue; 1601 } 1602 userGroups.emplace_back(grp); 1603 } 1604 1605 // Make sure user specified groups are valid. This is internal error because 1606 // it some inconsistencies between user manager and bmcweb. 1607 if (!accountTypeUserGroups.empty() && 1608 accountTypeUserGroups.size() != userGroups.size()) 1609 { 1610 messages::internalError(asyncResp->res); 1611 return; 1612 } 1613 crow::connections::systemBus->async_method_call( 1614 [asyncResp, username, password](const boost::system::error_code& ec2, 1615 sdbusplus::message_t& m) { 1616 processAfterCreateUser(asyncResp, username, password, ec2, m); 1617 }, 1618 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1619 "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups, 1620 roleId, enabled); 1621 } 1622 1623 inline void handleAccountCollectionPost( 1624 App& app, const crow::Request& req, 1625 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1626 { 1627 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1628 { 1629 return; 1630 } 1631 std::string username; 1632 std::string password; 1633 std::optional<std::string> roleIdJson; 1634 std::optional<bool> enabledJson; 1635 std::optional<std::vector<std::string>> accountTypes; 1636 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, 1637 "Password", password, "RoleId", roleIdJson, 1638 "Enabled", enabledJson, "AccountTypes", 1639 accountTypes)) 1640 { 1641 return; 1642 } 1643 1644 std::string roleId = roleIdJson.value_or("User"); 1645 std::string priv = getPrivilegeFromRoleId(roleId); 1646 if (priv.empty()) 1647 { 1648 messages::propertyValueNotInList(asyncResp->res, roleId, "RoleId"); 1649 return; 1650 } 1651 roleId = priv; 1652 1653 bool enabled = enabledJson.value_or(true); 1654 1655 // Reading AllGroups property 1656 sdbusplus::asio::getProperty<std::vector<std::string>>( 1657 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1658 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", 1659 "AllGroups", 1660 [asyncResp, username, password{std::move(password)}, roleId, enabled, 1661 accountTypes](const boost::system::error_code& ec, 1662 const std::vector<std::string>& allGroupsList) { 1663 if (ec) 1664 { 1665 BMCWEB_LOG_DEBUG("ERROR with async_method_call"); 1666 messages::internalError(asyncResp->res); 1667 return; 1668 } 1669 1670 if (allGroupsList.empty()) 1671 { 1672 messages::internalError(asyncResp->res); 1673 return; 1674 } 1675 1676 processAfterGetAllGroups(asyncResp, username, password, roleId, enabled, 1677 accountTypes, allGroupsList); 1678 }); 1679 } 1680 1681 inline void 1682 handleAccountHead(App& app, const crow::Request& req, 1683 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1684 const std::string& /*accountName*/) 1685 { 1686 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1687 { 1688 return; 1689 } 1690 asyncResp->res.addHeader( 1691 boost::beast::http::field::link, 1692 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1693 } 1694 1695 inline void 1696 handleAccountGet(App& app, const crow::Request& req, 1697 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1698 const std::string& accountName) 1699 { 1700 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1701 { 1702 return; 1703 } 1704 asyncResp->res.addHeader( 1705 boost::beast::http::field::link, 1706 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1707 1708 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1709 // If authentication is disabled, there are no user accounts 1710 messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName); 1711 return; 1712 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1713 1714 if (req.session == nullptr) 1715 { 1716 messages::internalError(asyncResp->res); 1717 return; 1718 } 1719 if (req.session->username != accountName) 1720 { 1721 // At this point we've determined that the user is trying to 1722 // modify a user that isn't them. We need to verify that they 1723 // have permissions to modify other users, so re-run the auth 1724 // check with the same permissions, minus ConfigureSelf. 1725 Privileges effectiveUserPrivileges = 1726 redfish::getUserPrivileges(*req.session); 1727 Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers", 1728 "ConfigureManager"}; 1729 if (!effectiveUserPrivileges.isSupersetOf( 1730 requiredPermissionsToChangeNonSelf)) 1731 { 1732 BMCWEB_LOG_DEBUG("GET Account denied access"); 1733 messages::insufficientPrivilege(asyncResp->res); 1734 return; 1735 } 1736 } 1737 1738 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1739 dbus::utility::getManagedObjects( 1740 "xyz.openbmc_project.User.Manager", path, 1741 [asyncResp, 1742 accountName](const boost::system::error_code& ec, 1743 const dbus::utility::ManagedObjectType& users) { 1744 if (ec) 1745 { 1746 messages::internalError(asyncResp->res); 1747 return; 1748 } 1749 const auto userIt = std::ranges::find_if( 1750 users, 1751 [accountName]( 1752 const std::pair<sdbusplus::message::object_path, 1753 dbus::utility::DBusInterfacesMap>& user) { 1754 return accountName == user.first.filename(); 1755 }); 1756 1757 if (userIt == users.end()) 1758 { 1759 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1760 accountName); 1761 return; 1762 } 1763 1764 asyncResp->res.jsonValue["@odata.type"] = 1765 "#ManagerAccount.v1_7_0.ManagerAccount"; 1766 asyncResp->res.jsonValue["Name"] = "User Account"; 1767 asyncResp->res.jsonValue["Description"] = "User Account"; 1768 asyncResp->res.jsonValue["Password"] = nullptr; 1769 asyncResp->res.jsonValue["StrictAccountTypes"] = true; 1770 1771 for (const auto& interface : userIt->second) 1772 { 1773 if (interface.first == "xyz.openbmc_project.User.Attributes") 1774 { 1775 for (const auto& property : interface.second) 1776 { 1777 if (property.first == "UserEnabled") 1778 { 1779 const bool* userEnabled = 1780 std::get_if<bool>(&property.second); 1781 if (userEnabled == nullptr) 1782 { 1783 BMCWEB_LOG_ERROR("UserEnabled wasn't a bool"); 1784 messages::internalError(asyncResp->res); 1785 return; 1786 } 1787 asyncResp->res.jsonValue["Enabled"] = *userEnabled; 1788 } 1789 else if (property.first == "UserLockedForFailedAttempt") 1790 { 1791 const bool* userLocked = 1792 std::get_if<bool>(&property.second); 1793 if (userLocked == nullptr) 1794 { 1795 BMCWEB_LOG_ERROR("UserLockedForF" 1796 "ailedAttempt " 1797 "wasn't a bool"); 1798 messages::internalError(asyncResp->res); 1799 return; 1800 } 1801 asyncResp->res.jsonValue["Locked"] = *userLocked; 1802 asyncResp->res 1803 .jsonValue["Locked@Redfish.AllowableValues"] = { 1804 "false"}; // can only unlock accounts 1805 } 1806 else if (property.first == "UserPrivilege") 1807 { 1808 const std::string* userPrivPtr = 1809 std::get_if<std::string>(&property.second); 1810 if (userPrivPtr == nullptr) 1811 { 1812 BMCWEB_LOG_ERROR("UserPrivilege wasn't a " 1813 "string"); 1814 messages::internalError(asyncResp->res); 1815 return; 1816 } 1817 std::string role = getRoleIdFromPrivilege(*userPrivPtr); 1818 if (role.empty()) 1819 { 1820 BMCWEB_LOG_ERROR("Invalid user role"); 1821 messages::internalError(asyncResp->res); 1822 return; 1823 } 1824 asyncResp->res.jsonValue["RoleId"] = role; 1825 1826 nlohmann::json& roleEntry = 1827 asyncResp->res.jsonValue["Links"]["Role"]; 1828 roleEntry["@odata.id"] = boost::urls::format( 1829 "/redfish/v1/AccountService/Roles/{}", role); 1830 } 1831 else if (property.first == "UserPasswordExpired") 1832 { 1833 const bool* userPasswordExpired = 1834 std::get_if<bool>(&property.second); 1835 if (userPasswordExpired == nullptr) 1836 { 1837 BMCWEB_LOG_ERROR( 1838 "UserPasswordExpired wasn't a bool"); 1839 messages::internalError(asyncResp->res); 1840 return; 1841 } 1842 asyncResp->res.jsonValue["PasswordChangeRequired"] = 1843 *userPasswordExpired; 1844 } 1845 else if (property.first == "UserGroups") 1846 { 1847 const std::vector<std::string>* userGroups = 1848 std::get_if<std::vector<std::string>>( 1849 &property.second); 1850 if (userGroups == nullptr) 1851 { 1852 BMCWEB_LOG_ERROR( 1853 "userGroups wasn't a string vector"); 1854 messages::internalError(asyncResp->res); 1855 return; 1856 } 1857 if (!translateUserGroup(*userGroups, asyncResp->res)) 1858 { 1859 BMCWEB_LOG_ERROR("userGroups mapping failed"); 1860 messages::internalError(asyncResp->res); 1861 return; 1862 } 1863 } 1864 } 1865 } 1866 } 1867 1868 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1869 "/redfish/v1/AccountService/Accounts/{}", accountName); 1870 asyncResp->res.jsonValue["Id"] = accountName; 1871 asyncResp->res.jsonValue["UserName"] = accountName; 1872 }); 1873 } 1874 1875 inline void 1876 handleAccountDelete(App& app, const crow::Request& req, 1877 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1878 const std::string& username) 1879 { 1880 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1881 { 1882 return; 1883 } 1884 1885 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1886 // If authentication is disabled, there are no user accounts 1887 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 1888 return; 1889 1890 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1891 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1892 tempObjPath /= username; 1893 const std::string userPath(tempObjPath); 1894 1895 crow::connections::systemBus->async_method_call( 1896 [asyncResp, username](const boost::system::error_code& ec) { 1897 if (ec) 1898 { 1899 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1900 username); 1901 return; 1902 } 1903 1904 messages::accountRemoved(asyncResp->res); 1905 }, 1906 "xyz.openbmc_project.User.Manager", userPath, 1907 "xyz.openbmc_project.Object.Delete", "Delete"); 1908 } 1909 1910 inline void 1911 handleAccountPatch(App& app, const crow::Request& req, 1912 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1913 const std::string& username) 1914 { 1915 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1916 { 1917 return; 1918 } 1919 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1920 // If authentication is disabled, there are no user accounts 1921 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 1922 return; 1923 1924 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1925 std::optional<std::string> newUserName; 1926 std::optional<std::string> password; 1927 std::optional<bool> enabled; 1928 std::optional<std::string> roleId; 1929 std::optional<bool> locked; 1930 std::optional<std::vector<std::string>> accountTypes; 1931 1932 if (req.session == nullptr) 1933 { 1934 messages::internalError(asyncResp->res); 1935 return; 1936 } 1937 1938 bool userSelf = (username == req.session->username); 1939 1940 Privileges effectiveUserPrivileges = 1941 redfish::getUserPrivileges(*req.session); 1942 Privileges configureUsers = {"ConfigureUsers"}; 1943 bool userHasConfigureUsers = 1944 effectiveUserPrivileges.isSupersetOf(configureUsers); 1945 if (userHasConfigureUsers) 1946 { 1947 // Users with ConfigureUsers can modify for all users 1948 if (!json_util::readJsonPatch( 1949 req, asyncResp->res, "UserName", newUserName, "Password", 1950 password, "RoleId", roleId, "Enabled", enabled, "Locked", 1951 locked, "AccountTypes", accountTypes)) 1952 { 1953 return; 1954 } 1955 } 1956 else 1957 { 1958 // ConfigureSelf accounts can only modify their own account 1959 if (!userSelf) 1960 { 1961 messages::insufficientPrivilege(asyncResp->res); 1962 return; 1963 } 1964 1965 // ConfigureSelf accounts can only modify their password 1966 if (!json_util::readJsonPatch(req, asyncResp->res, "Password", 1967 password)) 1968 { 1969 return; 1970 } 1971 } 1972 1973 // if user name is not provided in the patch method or if it 1974 // matches the user name in the URI, then we are treating it as 1975 // updating user properties other then username. If username 1976 // provided doesn't match the URI, then we are treating this as 1977 // user rename request. 1978 if (!newUserName || (newUserName.value() == username)) 1979 { 1980 updateUserProperties(asyncResp, username, password, enabled, roleId, 1981 locked, accountTypes, userSelf); 1982 return; 1983 } 1984 crow::connections::systemBus->async_method_call( 1985 [asyncResp, username, password(std::move(password)), 1986 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, 1987 locked, userSelf, accountTypes(std::move(accountTypes))]( 1988 const boost::system::error_code& ec, sdbusplus::message_t& m) { 1989 if (ec) 1990 { 1991 userErrorMessageHandler(m.get_error(), asyncResp, newUser, 1992 username); 1993 return; 1994 } 1995 1996 updateUserProperties(asyncResp, newUser, password, enabled, roleId, 1997 locked, accountTypes, userSelf); 1998 }, 1999 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 2000 "xyz.openbmc_project.User.Manager", "RenameUser", username, 2001 *newUserName); 2002 } 2003 2004 inline void requestAccountServiceRoutes(App& app) 2005 { 2006 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2007 .privileges(redfish::privileges::headAccountService) 2008 .methods(boost::beast::http::verb::head)( 2009 std::bind_front(handleAccountServiceHead, std::ref(app))); 2010 2011 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2012 .privileges(redfish::privileges::getAccountService) 2013 .methods(boost::beast::http::verb::get)( 2014 std::bind_front(handleAccountServiceGet, std::ref(app))); 2015 2016 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2017 .privileges(redfish::privileges::patchAccountService) 2018 .methods(boost::beast::http::verb::patch)( 2019 std::bind_front(handleAccountServicePatch, std::ref(app))); 2020 2021 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2022 .privileges(redfish::privileges::headManagerAccountCollection) 2023 .methods(boost::beast::http::verb::head)( 2024 std::bind_front(handleAccountCollectionHead, std::ref(app))); 2025 2026 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2027 .privileges(redfish::privileges::getManagerAccountCollection) 2028 .methods(boost::beast::http::verb::get)( 2029 std::bind_front(handleAccountCollectionGet, std::ref(app))); 2030 2031 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2032 .privileges(redfish::privileges::postManagerAccountCollection) 2033 .methods(boost::beast::http::verb::post)( 2034 std::bind_front(handleAccountCollectionPost, std::ref(app))); 2035 2036 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2037 .privileges(redfish::privileges::headManagerAccount) 2038 .methods(boost::beast::http::verb::head)( 2039 std::bind_front(handleAccountHead, std::ref(app))); 2040 2041 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2042 .privileges(redfish::privileges::getManagerAccount) 2043 .methods(boost::beast::http::verb::get)( 2044 std::bind_front(handleAccountGet, std::ref(app))); 2045 2046 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2047 // TODO this privilege should be using the generated endpoints, but 2048 // because of the special handling of ConfigureSelf, it's not able to 2049 // yet 2050 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) 2051 .methods(boost::beast::http::verb::patch)( 2052 std::bind_front(handleAccountPatch, std::ref(app))); 2053 2054 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2055 .privileges(redfish::privileges::deleteManagerAccount) 2056 .methods(boost::beast::http::verb::delete_)( 2057 std::bind_front(handleAccountDelete, std::ref(app))); 2058 } 2059 2060 } // namespace redfish 2061