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