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