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