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