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 <utility> 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 const std::shared_ptr<persistent_data::UserSession>& session) 1078 { 1079 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1080 tempObjPath /= username; 1081 std::string dbusObjectPath(tempObjPath); 1082 1083 dbus::utility::checkDbusPathExists( 1084 dbusObjectPath, 1085 [dbusObjectPath, username, password, roleId, enabled, locked, 1086 accountTypes(std::move(accountTypes)), userSelf, session, 1087 asyncResp{std::move(asyncResp)}](int rc) { 1088 if (rc <= 0) 1089 { 1090 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1091 username); 1092 return; 1093 } 1094 1095 if (password) 1096 { 1097 int retval = pamUpdatePassword(username, *password); 1098 1099 if (retval == PAM_USER_UNKNOWN) 1100 { 1101 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1102 username); 1103 } 1104 else if (retval == PAM_AUTHTOK_ERR) 1105 { 1106 // If password is invalid 1107 messages::propertyValueFormatError(asyncResp->res, nullptr, 1108 "Password"); 1109 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1110 } 1111 else if (retval != PAM_SUCCESS) 1112 { 1113 messages::internalError(asyncResp->res); 1114 return; 1115 } 1116 else 1117 { 1118 // Remove existing sessions of the user when password changed 1119 persistent_data::SessionStore::getInstance() 1120 .removeSessionsByUsernameExceptSession(username, session); 1121 messages::success(asyncResp->res); 1122 } 1123 } 1124 1125 if (enabled) 1126 { 1127 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1128 dbusObjectPath, 1129 "xyz.openbmc_project.User.Attributes", 1130 "UserEnabled", "Enabled", *enabled); 1131 } 1132 1133 if (roleId) 1134 { 1135 std::string priv = getPrivilegeFromRoleId(*roleId); 1136 if (priv.empty()) 1137 { 1138 messages::propertyValueNotInList(asyncResp->res, true, 1139 "Locked"); 1140 return; 1141 } 1142 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1143 dbusObjectPath, 1144 "xyz.openbmc_project.User.Attributes", 1145 "UserPrivilege", "RoleId", priv); 1146 } 1147 1148 if (locked) 1149 { 1150 // admin can unlock the account which is locked by 1151 // successive authentication failures but admin should 1152 // not be allowed to lock an account. 1153 if (*locked) 1154 { 1155 messages::propertyValueNotInList(asyncResp->res, "true", 1156 "Locked"); 1157 return; 1158 } 1159 setDbusProperty(asyncResp, "xyz.openbmc_project.User.Manager", 1160 dbusObjectPath, 1161 "xyz.openbmc_project.User.Attributes", 1162 "UserLockedForFailedAttempt", "Locked", *locked); 1163 } 1164 1165 if (accountTypes) 1166 { 1167 patchAccountTypes(*accountTypes, asyncResp, dbusObjectPath, 1168 userSelf); 1169 } 1170 }); 1171 } 1172 1173 inline void handleAccountServiceHead( 1174 App& app, const crow::Request& req, 1175 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1176 { 1177 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1178 { 1179 return; 1180 } 1181 asyncResp->res.addHeader( 1182 boost::beast::http::field::link, 1183 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1184 } 1185 1186 inline void 1187 getClientCertificates(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1188 const nlohmann::json::json_pointer& keyLocation) 1189 { 1190 boost::urls::url url( 1191 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates"); 1192 std::array<std::string_view, 1> interfaces = { 1193 "xyz.openbmc_project.Certs.Certificate"}; 1194 std::string path = "/xyz/openbmc_project/certs/authority/truststore"; 1195 1196 collection_util::getCollectionToKey(asyncResp, url, interfaces, path, 1197 keyLocation); 1198 } 1199 1200 inline void handleAccountServiceClientCertificatesInstanceHead( 1201 App& app, const crow::Request& req, 1202 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1203 const std::string& /*id*/) 1204 { 1205 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1206 { 1207 return; 1208 } 1209 1210 asyncResp->res.addHeader( 1211 boost::beast::http::field::link, 1212 "</redfish/v1/JsonSchemas/Certificate/Certificate.json>; rel=describedby"); 1213 } 1214 1215 inline void handleAccountServiceClientCertificatesInstanceGet( 1216 App& app, const crow::Request& req, 1217 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1218 { 1219 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1220 { 1221 return; 1222 } 1223 BMCWEB_LOG_DEBUG("ClientCertificate Certificate ID={}", id); 1224 const boost::urls::url certURL = boost::urls::format( 1225 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/{}", 1226 id); 1227 std::string objPath = 1228 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1229 getCertificateProperties( 1230 asyncResp, objPath, 1231 "xyz.openbmc_project.Certs.Manager.Authority.Truststore", id, certURL, 1232 "Client Certificate"); 1233 } 1234 1235 inline void handleAccountServiceClientCertificatesHead( 1236 App& app, const crow::Request& req, 1237 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1238 { 1239 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1240 { 1241 return; 1242 } 1243 1244 asyncResp->res.addHeader( 1245 boost::beast::http::field::link, 1246 "</redfish/v1/JsonSchemas/CertificateCollection/CertificateCollection.json>; rel=describedby"); 1247 } 1248 1249 inline void handleAccountServiceClientCertificatesGet( 1250 App& app, const crow::Request& req, 1251 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1252 { 1253 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1254 { 1255 return; 1256 } 1257 getClientCertificates(asyncResp, "/Members"_json_pointer); 1258 } 1259 1260 inline void 1261 handleAccountServiceGet(App& app, const crow::Request& req, 1262 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1263 { 1264 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1265 { 1266 return; 1267 } 1268 1269 if (req.session == nullptr) 1270 { 1271 messages::internalError(asyncResp->res); 1272 return; 1273 } 1274 1275 const persistent_data::AuthConfigMethods& authMethodsConfig = 1276 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1277 1278 asyncResp->res.addHeader( 1279 boost::beast::http::field::link, 1280 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1281 1282 nlohmann::json& json = asyncResp->res.jsonValue; 1283 json["@odata.id"] = "/redfish/v1/AccountService"; 1284 json["@odata.type"] = "#AccountService.v1_15_0.AccountService"; 1285 json["Id"] = "AccountService"; 1286 json["Name"] = "Account Service"; 1287 json["Description"] = "Account Service"; 1288 json["ServiceEnabled"] = true; 1289 json["MaxPasswordLength"] = 20; 1290 json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; 1291 json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; 1292 json["HTTPBasicAuth"] = authMethodsConfig.basic 1293 ? account_service::BasicAuthState::Enabled 1294 : account_service::BasicAuthState::Disabled; 1295 1296 nlohmann::json::array_t allowed; 1297 allowed.emplace_back(account_service::BasicAuthState::Enabled); 1298 allowed.emplace_back(account_service::BasicAuthState::Disabled); 1299 json["HTTPBasicAuth@AllowableValues"] = std::move(allowed); 1300 1301 nlohmann::json::object_t clientCertificate; 1302 clientCertificate["Enabled"] = authMethodsConfig.tls; 1303 clientCertificate["RespondToUnauthenticatedClients"] = true; 1304 clientCertificate["CertificateMappingAttribute"] = 1305 account_service::CertificateMappingAttribute::CommonName; 1306 nlohmann::json::object_t certificates; 1307 certificates["@odata.id"] = 1308 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates"; 1309 certificates["@odata.type"] = 1310 "#CertificateCollection.CertificateCollection"; 1311 clientCertificate["Certificates"] = std::move(certificates); 1312 json["MultiFactorAuth"]["ClientCertificate"] = std::move(clientCertificate); 1313 1314 getClientCertificates( 1315 asyncResp, 1316 "/MultiFactorAuth/ClientCertificate/Certificates/Members"_json_pointer); 1317 1318 json["Oem"]["OpenBMC"]["@odata.type"] = 1319 "#OpenBMCAccountService.v1_0_0.AccountService"; 1320 json["Oem"]["OpenBMC"]["@odata.id"] = 1321 "/redfish/v1/AccountService#/Oem/OpenBMC"; 1322 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = 1323 authMethodsConfig.basic; 1324 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = 1325 authMethodsConfig.sessionToken; 1326 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; 1327 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; 1328 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; 1329 1330 // /redfish/v1/AccountService/LDAP/Certificates is something only 1331 // ConfigureManager can access then only display when the user has 1332 // permissions ConfigureManager 1333 Privileges effectiveUserPrivileges = 1334 redfish::getUserPrivileges(*req.session); 1335 1336 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 1337 effectiveUserPrivileges)) 1338 { 1339 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = 1340 "/redfish/v1/AccountService/LDAP/Certificates"; 1341 } 1342 sdbusplus::asio::getAllProperties( 1343 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1344 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.AccountPolicy", 1345 [asyncResp](const boost::system::error_code& ec, 1346 const dbus::utility::DBusPropertiesMap& propertiesList) { 1347 if (ec) 1348 { 1349 messages::internalError(asyncResp->res); 1350 return; 1351 } 1352 1353 BMCWEB_LOG_DEBUG("Got {} properties for AccountService", 1354 propertiesList.size()); 1355 1356 const uint8_t* minPasswordLength = nullptr; 1357 const uint32_t* accountUnlockTimeout = nullptr; 1358 const uint16_t* maxLoginAttemptBeforeLockout = nullptr; 1359 1360 const bool success = sdbusplus::unpackPropertiesNoThrow( 1361 dbus_utils::UnpackErrorPrinter(), propertiesList, 1362 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout", 1363 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout", 1364 maxLoginAttemptBeforeLockout); 1365 1366 if (!success) 1367 { 1368 messages::internalError(asyncResp->res); 1369 return; 1370 } 1371 1372 if (minPasswordLength != nullptr) 1373 { 1374 asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength; 1375 } 1376 1377 if (accountUnlockTimeout != nullptr) 1378 { 1379 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1380 *accountUnlockTimeout; 1381 } 1382 1383 if (maxLoginAttemptBeforeLockout != nullptr) 1384 { 1385 asyncResp->res.jsonValue["AccountLockoutThreshold"] = 1386 *maxLoginAttemptBeforeLockout; 1387 } 1388 }); 1389 1390 auto callback = [asyncResp](bool success, const LDAPConfigData& confData, 1391 const std::string& ldapType) { 1392 if (!success) 1393 { 1394 return; 1395 } 1396 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1397 }; 1398 1399 getLDAPConfigData("LDAP", callback); 1400 getLDAPConfigData("ActiveDirectory", callback); 1401 } 1402 1403 inline void handleAccountServicePatch( 1404 App& app, const crow::Request& req, 1405 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1406 { 1407 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1408 { 1409 return; 1410 } 1411 std::optional<uint32_t> unlockTimeout; 1412 std::optional<uint16_t> lockoutThreshold; 1413 std::optional<uint8_t> minPasswordLength; 1414 std::optional<uint16_t> maxPasswordLength; 1415 LdapPatchParams ldapObject; 1416 LdapPatchParams activeDirectoryObject; 1417 AuthMethods auth; 1418 std::optional<std::string> httpBasicAuth; 1419 // clang-format off 1420 if (!json_util::readJsonPatch( 1421 req, asyncResp->res, 1422 "AccountLockoutDuration", unlockTimeout, 1423 "AccountLockoutThreshold", lockoutThreshold, 1424 "ActiveDirectory/Authentication/AuthenticationType", activeDirectoryObject.authType, 1425 "ActiveDirectory/Authentication/Password", activeDirectoryObject.password, 1426 "ActiveDirectory/Authentication/Username", activeDirectoryObject.userName, 1427 "ActiveDirectory/LDAPService/SearchSettings/BaseDistinguishedNames", activeDirectoryObject.baseDNList, 1428 "ActiveDirectory/LDAPService/SearchSettings/GroupsAttribute", activeDirectoryObject.groupsAttribute, 1429 "ActiveDirectory/LDAPService/SearchSettings/UsernameAttribute", activeDirectoryObject.userNameAttribute, 1430 "ActiveDirectory/RemoteRoleMapping", activeDirectoryObject.remoteRoleMapData, 1431 "ActiveDirectory/ServiceAddresses", activeDirectoryObject.serviceAddressList, 1432 "ActiveDirectory/ServiceEnabled", activeDirectoryObject.serviceEnabled, 1433 "LDAP/Authentication/AuthenticationType", ldapObject.authType, 1434 "LDAP/Authentication/Password", ldapObject.password, 1435 "LDAP/Authentication/Username", ldapObject.userName, 1436 "LDAP/LDAPService/SearchSettings/BaseDistinguishedNames", ldapObject.baseDNList, 1437 "LDAP/LDAPService/SearchSettings/GroupsAttribute", ldapObject.groupsAttribute, 1438 "LDAP/LDAPService/SearchSettings/UsernameAttribute", ldapObject.userNameAttribute, 1439 "LDAP/RemoteRoleMapping", ldapObject.remoteRoleMapData, 1440 "LDAP/ServiceAddresses", ldapObject.serviceAddressList, 1441 "LDAP/ServiceEnabled", ldapObject.serviceEnabled, 1442 "MaxPasswordLength", maxPasswordLength, 1443 "MinPasswordLength", minPasswordLength, 1444 "Oem/OpenBMC/AuthMethods/BasicAuth", auth.basicAuth, 1445 "Oem/OpenBMC/AuthMethods/Cookie", auth.cookie, 1446 "Oem/OpenBMC/AuthMethods/SessionToken", auth.sessionToken, 1447 "Oem/OpenBMC/AuthMethods/TLS", auth.tls, 1448 "Oem/OpenBMC/AuthMethods/XToken", auth.xToken, 1449 "HTTPBasicAuth", httpBasicAuth)) 1450 { 1451 return; 1452 } 1453 // clang-format on 1454 1455 if (httpBasicAuth) 1456 { 1457 if (*httpBasicAuth == "Enabled") 1458 { 1459 auth.basicAuth = true; 1460 } 1461 else if (*httpBasicAuth == "Disabled") 1462 { 1463 auth.basicAuth = false; 1464 } 1465 else 1466 { 1467 messages::propertyValueNotInList(asyncResp->res, "HttpBasicAuth", 1468 *httpBasicAuth); 1469 } 1470 } 1471 1472 if (minPasswordLength) 1473 { 1474 setDbusProperty( 1475 asyncResp, "xyz.openbmc_project.User.Manager", 1476 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1477 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", 1478 "MinPasswordLength", *minPasswordLength); 1479 } 1480 1481 if (maxPasswordLength) 1482 { 1483 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1484 } 1485 1486 handleLDAPPatch(std::move(activeDirectoryObject), asyncResp, 1487 "ActiveDirectory"); 1488 handleLDAPPatch(std::move(ldapObject), asyncResp, "LDAP"); 1489 1490 handleAuthMethodsPatch(asyncResp, auth); 1491 1492 if (unlockTimeout) 1493 { 1494 setDbusProperty( 1495 asyncResp, "xyz.openbmc_project.User.Manager", 1496 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1497 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", 1498 "AccountLockoutDuration", *unlockTimeout); 1499 } 1500 if (lockoutThreshold) 1501 { 1502 setDbusProperty( 1503 asyncResp, "xyz.openbmc_project.User.Manager", 1504 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1505 "xyz.openbmc_project.User.AccountPolicy", 1506 "MaxLoginAttemptBeforeLockout", "AccountLockoutThreshold", 1507 *lockoutThreshold); 1508 } 1509 } 1510 1511 inline void handleAccountCollectionHead( 1512 App& app, const crow::Request& req, 1513 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1514 { 1515 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1516 { 1517 return; 1518 } 1519 asyncResp->res.addHeader( 1520 boost::beast::http::field::link, 1521 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1522 } 1523 1524 inline void handleAccountCollectionGet( 1525 App& app, const crow::Request& req, 1526 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1527 { 1528 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1529 { 1530 return; 1531 } 1532 1533 if (req.session == nullptr) 1534 { 1535 messages::internalError(asyncResp->res); 1536 return; 1537 } 1538 1539 asyncResp->res.addHeader( 1540 boost::beast::http::field::link, 1541 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1542 1543 asyncResp->res.jsonValue["@odata.id"] = 1544 "/redfish/v1/AccountService/Accounts"; 1545 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." 1546 "ManagerAccountCollection"; 1547 asyncResp->res.jsonValue["Name"] = "Accounts Collection"; 1548 asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; 1549 1550 Privileges effectiveUserPrivileges = 1551 redfish::getUserPrivileges(*req.session); 1552 1553 std::string thisUser; 1554 if (req.session) 1555 { 1556 thisUser = req.session->username; 1557 } 1558 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1559 dbus::utility::getManagedObjects( 1560 "xyz.openbmc_project.User.Manager", path, 1561 [asyncResp, thisUser, effectiveUserPrivileges]( 1562 const boost::system::error_code& ec, 1563 const dbus::utility::ManagedObjectType& users) { 1564 if (ec) 1565 { 1566 messages::internalError(asyncResp->res); 1567 return; 1568 } 1569 1570 bool userCanSeeAllAccounts = 1571 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); 1572 1573 bool userCanSeeSelf = 1574 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); 1575 1576 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 1577 memberArray = nlohmann::json::array(); 1578 1579 for (const auto& userpath : users) 1580 { 1581 std::string user = userpath.first.filename(); 1582 if (user.empty()) 1583 { 1584 messages::internalError(asyncResp->res); 1585 BMCWEB_LOG_ERROR("Invalid firmware ID"); 1586 1587 return; 1588 } 1589 1590 // As clarified by Redfish here: 1591 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1592 // Users without ConfigureUsers, only see their own 1593 // account. Users with ConfigureUsers, see all 1594 // accounts. 1595 if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf)) 1596 { 1597 nlohmann::json::object_t member; 1598 member["@odata.id"] = boost::urls::format( 1599 "/redfish/v1/AccountService/Accounts/{}", user); 1600 memberArray.emplace_back(std::move(member)); 1601 } 1602 } 1603 asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); 1604 }); 1605 } 1606 1607 inline void processAfterCreateUser( 1608 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1609 const std::string& username, const std::string& password, 1610 const boost::system::error_code& ec, sdbusplus::message_t& m) 1611 { 1612 if (ec) 1613 { 1614 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); 1615 return; 1616 } 1617 1618 if (pamUpdatePassword(username, password) != PAM_SUCCESS) 1619 { 1620 // At this point we have a user that's been 1621 // created, but the password set 1622 // failed.Something is wrong, so delete the user 1623 // that we've already created 1624 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1625 tempObjPath /= username; 1626 const std::string userPath(tempObjPath); 1627 1628 crow::connections::systemBus->async_method_call( 1629 [asyncResp, password](const boost::system::error_code& ec3) { 1630 if (ec3) 1631 { 1632 messages::internalError(asyncResp->res); 1633 return; 1634 } 1635 1636 // If password is invalid 1637 messages::propertyValueFormatError(asyncResp->res, nullptr, 1638 "Password"); 1639 }, 1640 "xyz.openbmc_project.User.Manager", userPath, 1641 "xyz.openbmc_project.Object.Delete", "Delete"); 1642 1643 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1644 return; 1645 } 1646 1647 messages::created(asyncResp->res); 1648 asyncResp->res.addHeader("Location", 1649 "/redfish/v1/AccountService/Accounts/" + username); 1650 } 1651 1652 inline void processAfterGetAllGroups( 1653 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1654 const std::string& username, const std::string& password, 1655 const std::string& roleId, bool enabled, 1656 std::optional<std::vector<std::string>> accountTypes, 1657 const std::vector<std::string>& allGroupsList) 1658 { 1659 std::vector<std::string> userGroups; 1660 std::vector<std::string> accountTypeUserGroups; 1661 1662 // If user specified account types then convert them to unix user groups 1663 if (accountTypes) 1664 { 1665 if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes, 1666 accountTypeUserGroups)) 1667 { 1668 // Problem in mapping Account Types to User Groups, Error already 1669 // logged. 1670 return; 1671 } 1672 } 1673 1674 for (const auto& grp : allGroupsList) 1675 { 1676 // If user specified the account type then only accept groups which are 1677 // in the account types group list. 1678 if (!accountTypeUserGroups.empty()) 1679 { 1680 bool found = false; 1681 for (const auto& grp1 : accountTypeUserGroups) 1682 { 1683 if (grp == grp1) 1684 { 1685 found = true; 1686 break; 1687 } 1688 } 1689 if (!found) 1690 { 1691 continue; 1692 } 1693 } 1694 1695 // Console access is provided to the user who is a member of 1696 // hostconsole group and has a administrator role. So, set 1697 // hostconsole group only for the administrator. 1698 if ((grp == "hostconsole") && (roleId != "priv-admin")) 1699 { 1700 if (!accountTypeUserGroups.empty()) 1701 { 1702 BMCWEB_LOG_ERROR( 1703 "Only administrator can get HostConsole access"); 1704 asyncResp->res.result(boost::beast::http::status::bad_request); 1705 return; 1706 } 1707 continue; 1708 } 1709 userGroups.emplace_back(grp); 1710 } 1711 1712 // Make sure user specified groups are valid. This is internal error because 1713 // it some inconsistencies between user manager and bmcweb. 1714 if (!accountTypeUserGroups.empty() && 1715 accountTypeUserGroups.size() != userGroups.size()) 1716 { 1717 messages::internalError(asyncResp->res); 1718 return; 1719 } 1720 crow::connections::systemBus->async_method_call( 1721 [asyncResp, username, password](const boost::system::error_code& ec2, 1722 sdbusplus::message_t& m) { 1723 processAfterCreateUser(asyncResp, username, password, ec2, m); 1724 }, 1725 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1726 "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups, 1727 roleId, enabled); 1728 } 1729 1730 inline void handleAccountCollectionPost( 1731 App& app, const crow::Request& req, 1732 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1733 { 1734 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1735 { 1736 return; 1737 } 1738 std::string username; 1739 std::string password; 1740 std::optional<std::string> roleIdJson; 1741 std::optional<bool> enabledJson; 1742 std::optional<std::vector<std::string>> accountTypes; 1743 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, 1744 "Password", password, "RoleId", roleIdJson, 1745 "Enabled", enabledJson, "AccountTypes", 1746 accountTypes)) 1747 { 1748 return; 1749 } 1750 1751 std::string roleId = roleIdJson.value_or("User"); 1752 std::string priv = getPrivilegeFromRoleId(roleId); 1753 if (priv.empty()) 1754 { 1755 messages::propertyValueNotInList(asyncResp->res, roleId, "RoleId"); 1756 return; 1757 } 1758 roleId = priv; 1759 1760 bool enabled = enabledJson.value_or(true); 1761 1762 // Reading AllGroups property 1763 sdbusplus::asio::getProperty<std::vector<std::string>>( 1764 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1765 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", 1766 "AllGroups", 1767 [asyncResp, username, password{std::move(password)}, roleId, enabled, 1768 accountTypes](const boost::system::error_code& ec, 1769 const std::vector<std::string>& allGroupsList) { 1770 if (ec) 1771 { 1772 BMCWEB_LOG_DEBUG("ERROR with async_method_call"); 1773 messages::internalError(asyncResp->res); 1774 return; 1775 } 1776 1777 if (allGroupsList.empty()) 1778 { 1779 messages::internalError(asyncResp->res); 1780 return; 1781 } 1782 1783 processAfterGetAllGroups(asyncResp, username, password, roleId, enabled, 1784 accountTypes, allGroupsList); 1785 }); 1786 } 1787 1788 inline void 1789 handleAccountHead(App& app, const crow::Request& req, 1790 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1791 const std::string& /*accountName*/) 1792 { 1793 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1794 { 1795 return; 1796 } 1797 asyncResp->res.addHeader( 1798 boost::beast::http::field::link, 1799 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1800 } 1801 1802 inline void 1803 handleAccountGet(App& app, const crow::Request& req, 1804 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1805 const std::string& accountName) 1806 { 1807 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1808 { 1809 return; 1810 } 1811 asyncResp->res.addHeader( 1812 boost::beast::http::field::link, 1813 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1814 1815 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH) 1816 { 1817 // If authentication is disabled, there are no user accounts 1818 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1819 accountName); 1820 return; 1821 } 1822 1823 if (req.session == nullptr) 1824 { 1825 messages::internalError(asyncResp->res); 1826 return; 1827 } 1828 if (req.session->username != accountName) 1829 { 1830 // At this point we've determined that the user is trying to 1831 // modify a user that isn't them. We need to verify that they 1832 // have permissions to modify other users, so re-run the auth 1833 // check with the same permissions, minus ConfigureSelf. 1834 Privileges effectiveUserPrivileges = 1835 redfish::getUserPrivileges(*req.session); 1836 Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers", 1837 "ConfigureManager"}; 1838 if (!effectiveUserPrivileges.isSupersetOf( 1839 requiredPermissionsToChangeNonSelf)) 1840 { 1841 BMCWEB_LOG_DEBUG("GET Account denied access"); 1842 messages::insufficientPrivilege(asyncResp->res); 1843 return; 1844 } 1845 } 1846 1847 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1848 dbus::utility::getManagedObjects( 1849 "xyz.openbmc_project.User.Manager", path, 1850 [asyncResp, 1851 accountName](const boost::system::error_code& ec, 1852 const dbus::utility::ManagedObjectType& users) { 1853 if (ec) 1854 { 1855 messages::internalError(asyncResp->res); 1856 return; 1857 } 1858 const auto userIt = std::ranges::find_if( 1859 users, 1860 [accountName]( 1861 const std::pair<sdbusplus::message::object_path, 1862 dbus::utility::DBusInterfacesMap>& user) { 1863 return accountName == user.first.filename(); 1864 }); 1865 1866 if (userIt == users.end()) 1867 { 1868 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1869 accountName); 1870 return; 1871 } 1872 1873 asyncResp->res.jsonValue["@odata.type"] = 1874 "#ManagerAccount.v1_7_0.ManagerAccount"; 1875 asyncResp->res.jsonValue["Name"] = "User Account"; 1876 asyncResp->res.jsonValue["Description"] = "User Account"; 1877 asyncResp->res.jsonValue["Password"] = nullptr; 1878 asyncResp->res.jsonValue["StrictAccountTypes"] = true; 1879 1880 for (const auto& interface : userIt->second) 1881 { 1882 if (interface.first == "xyz.openbmc_project.User.Attributes") 1883 { 1884 for (const auto& property : interface.second) 1885 { 1886 if (property.first == "UserEnabled") 1887 { 1888 const bool* userEnabled = 1889 std::get_if<bool>(&property.second); 1890 if (userEnabled == nullptr) 1891 { 1892 BMCWEB_LOG_ERROR("UserEnabled wasn't a bool"); 1893 messages::internalError(asyncResp->res); 1894 return; 1895 } 1896 asyncResp->res.jsonValue["Enabled"] = *userEnabled; 1897 } 1898 else if (property.first == "UserLockedForFailedAttempt") 1899 { 1900 const bool* userLocked = 1901 std::get_if<bool>(&property.second); 1902 if (userLocked == nullptr) 1903 { 1904 BMCWEB_LOG_ERROR("UserLockedForF" 1905 "ailedAttempt " 1906 "wasn't a bool"); 1907 messages::internalError(asyncResp->res); 1908 return; 1909 } 1910 asyncResp->res.jsonValue["Locked"] = *userLocked; 1911 nlohmann::json::array_t allowed; 1912 // can only unlock accounts 1913 allowed.emplace_back("false"); 1914 asyncResp->res 1915 .jsonValue["Locked@Redfish.AllowableValues"] = 1916 std::move(allowed); 1917 } 1918 else if (property.first == "UserPrivilege") 1919 { 1920 const std::string* userPrivPtr = 1921 std::get_if<std::string>(&property.second); 1922 if (userPrivPtr == nullptr) 1923 { 1924 BMCWEB_LOG_ERROR("UserPrivilege wasn't a " 1925 "string"); 1926 messages::internalError(asyncResp->res); 1927 return; 1928 } 1929 std::string role = getRoleIdFromPrivilege(*userPrivPtr); 1930 if (role.empty()) 1931 { 1932 BMCWEB_LOG_ERROR("Invalid user role"); 1933 messages::internalError(asyncResp->res); 1934 return; 1935 } 1936 asyncResp->res.jsonValue["RoleId"] = role; 1937 1938 nlohmann::json& roleEntry = 1939 asyncResp->res.jsonValue["Links"]["Role"]; 1940 roleEntry["@odata.id"] = boost::urls::format( 1941 "/redfish/v1/AccountService/Roles/{}", role); 1942 } 1943 else if (property.first == "UserPasswordExpired") 1944 { 1945 const bool* userPasswordExpired = 1946 std::get_if<bool>(&property.second); 1947 if (userPasswordExpired == nullptr) 1948 { 1949 BMCWEB_LOG_ERROR( 1950 "UserPasswordExpired wasn't a bool"); 1951 messages::internalError(asyncResp->res); 1952 return; 1953 } 1954 asyncResp->res.jsonValue["PasswordChangeRequired"] = 1955 *userPasswordExpired; 1956 } 1957 else if (property.first == "UserGroups") 1958 { 1959 const std::vector<std::string>* userGroups = 1960 std::get_if<std::vector<std::string>>( 1961 &property.second); 1962 if (userGroups == nullptr) 1963 { 1964 BMCWEB_LOG_ERROR( 1965 "userGroups wasn't a string vector"); 1966 messages::internalError(asyncResp->res); 1967 return; 1968 } 1969 if (!translateUserGroup(*userGroups, asyncResp->res)) 1970 { 1971 BMCWEB_LOG_ERROR("userGroups mapping failed"); 1972 messages::internalError(asyncResp->res); 1973 return; 1974 } 1975 } 1976 } 1977 } 1978 } 1979 1980 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 1981 "/redfish/v1/AccountService/Accounts/{}", accountName); 1982 asyncResp->res.jsonValue["Id"] = accountName; 1983 asyncResp->res.jsonValue["UserName"] = accountName; 1984 }); 1985 } 1986 1987 inline void 1988 handleAccountDelete(App& app, const crow::Request& req, 1989 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1990 const std::string& username) 1991 { 1992 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1993 { 1994 return; 1995 } 1996 1997 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH) 1998 { 1999 // If authentication is disabled, there are no user accounts 2000 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 2001 return; 2002 } 2003 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 2004 tempObjPath /= username; 2005 const std::string userPath(tempObjPath); 2006 2007 crow::connections::systemBus->async_method_call( 2008 [asyncResp, username](const boost::system::error_code& ec) { 2009 if (ec) 2010 { 2011 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 2012 username); 2013 return; 2014 } 2015 2016 messages::accountRemoved(asyncResp->res); 2017 }, 2018 "xyz.openbmc_project.User.Manager", userPath, 2019 "xyz.openbmc_project.Object.Delete", "Delete"); 2020 } 2021 2022 inline void 2023 handleAccountPatch(App& app, const crow::Request& req, 2024 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2025 const std::string& username) 2026 { 2027 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2028 { 2029 return; 2030 } 2031 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH) 2032 { 2033 // If authentication is disabled, there are no user accounts 2034 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 2035 return; 2036 } 2037 std::optional<std::string> newUserName; 2038 std::optional<std::string> password; 2039 std::optional<bool> enabled; 2040 std::optional<std::string> roleId; 2041 std::optional<bool> locked; 2042 std::optional<std::vector<std::string>> accountTypes; 2043 2044 if (req.session == nullptr) 2045 { 2046 messages::internalError(asyncResp->res); 2047 return; 2048 } 2049 2050 bool userSelf = (username == req.session->username); 2051 2052 Privileges effectiveUserPrivileges = 2053 redfish::getUserPrivileges(*req.session); 2054 Privileges configureUsers = {"ConfigureUsers"}; 2055 bool userHasConfigureUsers = 2056 effectiveUserPrivileges.isSupersetOf(configureUsers); 2057 if (userHasConfigureUsers) 2058 { 2059 // Users with ConfigureUsers can modify for all users 2060 if (!json_util::readJsonPatch( 2061 req, asyncResp->res, "UserName", newUserName, "Password", 2062 password, "RoleId", roleId, "Enabled", enabled, "Locked", 2063 locked, "AccountTypes", accountTypes)) 2064 { 2065 return; 2066 } 2067 } 2068 else 2069 { 2070 // ConfigureSelf accounts can only modify their own account 2071 if (!userSelf) 2072 { 2073 messages::insufficientPrivilege(asyncResp->res); 2074 return; 2075 } 2076 2077 // ConfigureSelf accounts can only modify their password 2078 if (!json_util::readJsonPatch(req, asyncResp->res, "Password", 2079 password)) 2080 { 2081 return; 2082 } 2083 } 2084 2085 // if user name is not provided in the patch method or if it 2086 // matches the user name in the URI, then we are treating it as 2087 // updating user properties other then username. If username 2088 // provided doesn't match the URI, then we are treating this as 2089 // user rename request. 2090 if (!newUserName || (newUserName.value() == username)) 2091 { 2092 updateUserProperties(asyncResp, username, password, enabled, roleId, 2093 locked, accountTypes, userSelf, req.session); 2094 return; 2095 } 2096 crow::connections::systemBus->async_method_call( 2097 [asyncResp, username, password(std::move(password)), 2098 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, 2099 locked, userSelf, req, accountTypes(std::move(accountTypes))]( 2100 const boost::system::error_code& ec, sdbusplus::message_t& m) { 2101 if (ec) 2102 { 2103 userErrorMessageHandler(m.get_error(), asyncResp, newUser, 2104 username); 2105 return; 2106 } 2107 2108 updateUserProperties(asyncResp, newUser, password, enabled, roleId, 2109 locked, accountTypes, userSelf, req.session); 2110 }, 2111 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 2112 "xyz.openbmc_project.User.Manager", "RenameUser", username, 2113 *newUserName); 2114 } 2115 2116 inline void requestAccountServiceRoutes(App& app) 2117 { 2118 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2119 .privileges(redfish::privileges::headAccountService) 2120 .methods(boost::beast::http::verb::head)( 2121 std::bind_front(handleAccountServiceHead, std::ref(app))); 2122 2123 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2124 .privileges(redfish::privileges::getAccountService) 2125 .methods(boost::beast::http::verb::get)( 2126 std::bind_front(handleAccountServiceGet, std::ref(app))); 2127 2128 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2129 .privileges(redfish::privileges::patchAccountService) 2130 .methods(boost::beast::http::verb::patch)( 2131 std::bind_front(handleAccountServicePatch, std::ref(app))); 2132 2133 BMCWEB_ROUTE( 2134 app, 2135 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates") 2136 .privileges(redfish::privileges::headCertificateCollection) 2137 .methods(boost::beast::http::verb::head)(std::bind_front( 2138 handleAccountServiceClientCertificatesHead, std::ref(app))); 2139 2140 BMCWEB_ROUTE( 2141 app, 2142 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates") 2143 .privileges(redfish::privileges::getCertificateCollection) 2144 .methods(boost::beast::http::verb::get)(std::bind_front( 2145 handleAccountServiceClientCertificatesGet, std::ref(app))); 2146 2147 BMCWEB_ROUTE( 2148 app, 2149 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>") 2150 .privileges(redfish::privileges::headCertificate) 2151 .methods(boost::beast::http::verb::head)(std::bind_front( 2152 handleAccountServiceClientCertificatesInstanceHead, std::ref(app))); 2153 2154 BMCWEB_ROUTE( 2155 app, 2156 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/") 2157 .privileges(redfish::privileges::getCertificate) 2158 .methods(boost::beast::http::verb::get)(std::bind_front( 2159 handleAccountServiceClientCertificatesInstanceGet, std::ref(app))); 2160 2161 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2162 .privileges(redfish::privileges::headManagerAccountCollection) 2163 .methods(boost::beast::http::verb::head)( 2164 std::bind_front(handleAccountCollectionHead, std::ref(app))); 2165 2166 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2167 .privileges(redfish::privileges::getManagerAccountCollection) 2168 .methods(boost::beast::http::verb::get)( 2169 std::bind_front(handleAccountCollectionGet, std::ref(app))); 2170 2171 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2172 .privileges(redfish::privileges::postManagerAccountCollection) 2173 .methods(boost::beast::http::verb::post)( 2174 std::bind_front(handleAccountCollectionPost, std::ref(app))); 2175 2176 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2177 .privileges(redfish::privileges::headManagerAccount) 2178 .methods(boost::beast::http::verb::head)( 2179 std::bind_front(handleAccountHead, std::ref(app))); 2180 2181 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2182 .privileges(redfish::privileges::getManagerAccount) 2183 .methods(boost::beast::http::verb::get)( 2184 std::bind_front(handleAccountGet, std::ref(app))); 2185 2186 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2187 // TODO this privilege should be using the generated endpoints, but 2188 // because of the special handling of ConfigureSelf, it's not able to 2189 // yet 2190 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) 2191 .methods(boost::beast::http::verb::patch)( 2192 std::bind_front(handleAccountPatch, std::ref(app))); 2193 2194 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2195 .privileges(redfish::privileges::deleteManagerAccount) 2196 .methods(boost::beast::http::verb::delete_)( 2197 std::bind_front(handleAccountDelete, std::ref(app))); 2198 } 2199 2200 } // namespace redfish 2201