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