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