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