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