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