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