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 if (!confData.uri.empty()) 335 { 336 serviceAddresses.emplace_back(confData.uri); 337 } 338 ldap["ServiceAddresses"] = std::move(serviceAddresses); 339 340 nlohmann::json::object_t authentication; 341 authentication["AuthenticationType"] = 342 account_service::AuthenticationTypes::UsernameAndPassword; 343 authentication["Username"] = confData.bindDN; 344 authentication["Password"] = nullptr; 345 ldap["Authentication"] = std::move(authentication); 346 347 nlohmann::json::object_t ldapService; 348 nlohmann::json::object_t searchSettings; 349 nlohmann::json::array_t baseDistinguishedNames; 350 if (!confData.baseDN.empty()) 351 { 352 baseDistinguishedNames.emplace_back(confData.baseDN); 353 } 354 355 searchSettings["BaseDistinguishedNames"] = 356 std::move(baseDistinguishedNames); 357 searchSettings["UsernameAttribute"] = confData.userNameAttribute; 358 searchSettings["GroupsAttribute"] = confData.groupAttribute; 359 ldapService["SearchSettings"] = std::move(searchSettings); 360 ldap["LDAPService"] = std::move(ldapService); 361 362 nlohmann::json::array_t roleMapArray; 363 for (const auto& obj : confData.groupRoleList) 364 { 365 BMCWEB_LOG_DEBUG("Pushing the data groupName={}", obj.second.groupName); 366 367 nlohmann::json::object_t remoteGroup; 368 remoteGroup["RemoteGroup"] = obj.second.groupName; 369 remoteGroup["LocalRole"] = getRoleIdFromPrivilege(obj.second.privilege); 370 roleMapArray.emplace_back(std::move(remoteGroup)); 371 } 372 373 ldap["RemoteRoleMapping"] = std::move(roleMapArray); 374 375 jsonResponse[ldapType].update(ldap); 376 } 377 378 /** 379 * @brief validates given JSON input and then calls appropriate method to 380 * create, to delete or to set Rolemapping object based on the given input. 381 * 382 */ 383 inline void handleRoleMapPatch( 384 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 385 const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData, 386 const std::string& serverType, 387 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>& input) 388 { 389 for (size_t index = 0; index < input.size(); index++) 390 { 391 std::variant<nlohmann::json::object_t, std::nullptr_t>& thisJson = 392 input[index]; 393 nlohmann::json::object_t* obj = 394 std::get_if<nlohmann::json::object_t>(&thisJson); 395 if (obj == nullptr) 396 { 397 // delete the existing object 398 if (index < roleMapObjData.size()) 399 { 400 dbus::utility::async_method_call( 401 asyncResp, 402 [asyncResp, roleMapObjData, serverType, 403 index](const boost::system::error_code& ec) { 404 if (ec) 405 { 406 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 407 messages::internalError(asyncResp->res); 408 return; 409 } 410 asyncResp->res 411 .jsonValue[serverType]["RemoteRoleMapping"][index] = 412 nullptr; 413 }, 414 ldapDbusService, roleMapObjData[index].first, 415 "xyz.openbmc_project.Object.Delete", "Delete"); 416 } 417 else 418 { 419 BMCWEB_LOG_ERROR("Can't delete the object"); 420 messages::propertyValueTypeError( 421 asyncResp->res, "null", 422 "RemoteRoleMapping/" + std::to_string(index)); 423 return; 424 } 425 } 426 else if (obj->empty()) 427 { 428 // Don't do anything for the empty objects,parse next json 429 // eg {"RemoteRoleMapping",[{}]} 430 } 431 else 432 { 433 // update/create the object 434 std::optional<std::string> remoteGroup; 435 std::optional<std::string> localRole; 436 437 if (!json_util::readJsonObject( // 438 *obj, asyncResp->res, // 439 "LocalRole", localRole, // 440 "RemoteGroup", remoteGroup // 441 )) 442 { 443 continue; 444 } 445 446 // Update existing RoleMapping Object 447 if (index < roleMapObjData.size()) 448 { 449 BMCWEB_LOG_DEBUG("Update Role Map Object"); 450 // If "RemoteGroup" info is provided 451 if (remoteGroup) 452 { 453 setDbusProperty( 454 asyncResp, 455 std::format("RemoteRoleMapping/{}/RemoteGroup", index), 456 ldapDbusService, roleMapObjData[index].first, 457 "xyz.openbmc_project.User.PrivilegeMapperEntry", 458 "GroupName", *remoteGroup); 459 } 460 461 // If "LocalRole" info is provided 462 if (localRole) 463 { 464 std::string priv = getPrivilegeFromRoleId(*localRole); 465 if (priv.empty()) 466 { 467 messages::propertyValueNotInList( 468 asyncResp->res, *localRole, 469 std::format("RemoteRoleMapping/{}/LocalRole", 470 index)); 471 return; 472 } 473 setDbusProperty( 474 asyncResp, 475 std::format("RemoteRoleMapping/{}/LocalRole", index), 476 ldapDbusService, roleMapObjData[index].first, 477 "xyz.openbmc_project.User.PrivilegeMapperEntry", 478 "Privilege", priv); 479 } 480 } 481 // Create a new RoleMapping Object. 482 else 483 { 484 BMCWEB_LOG_DEBUG( 485 "setRoleMappingProperties: Creating new Object"); 486 std::string pathString = 487 "RemoteRoleMapping/" + std::to_string(index); 488 489 if (!localRole) 490 { 491 messages::propertyMissing(asyncResp->res, 492 pathString + "/LocalRole"); 493 continue; 494 } 495 if (!remoteGroup) 496 { 497 messages::propertyMissing(asyncResp->res, 498 pathString + "/RemoteGroup"); 499 continue; 500 } 501 502 std::string dbusObjectPath; 503 if (serverType == "ActiveDirectory") 504 { 505 dbusObjectPath = adConfigObject; 506 } 507 else if (serverType == "LDAP") 508 { 509 dbusObjectPath = ldapConfigObjectName; 510 } 511 512 BMCWEB_LOG_DEBUG("Remote Group={},LocalRole={}", *remoteGroup, 513 *localRole); 514 515 dbus::utility::async_method_call( 516 asyncResp, 517 [asyncResp, serverType, localRole, 518 remoteGroup](const boost::system::error_code& ec) { 519 if (ec) 520 { 521 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 522 messages::internalError(asyncResp->res); 523 return; 524 } 525 nlohmann::json& remoteRoleJson = 526 asyncResp->res 527 .jsonValue[serverType]["RemoteRoleMapping"]; 528 nlohmann::json::object_t roleMapEntry; 529 roleMapEntry["LocalRole"] = *localRole; 530 roleMapEntry["RemoteGroup"] = *remoteGroup; 531 remoteRoleJson.emplace_back(std::move(roleMapEntry)); 532 }, 533 ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, 534 "Create", *remoteGroup, 535 getPrivilegeFromRoleId(std::move(*localRole))); 536 } 537 } 538 } 539 } 540 541 /** 542 * Function that retrieves all properties for LDAP config object 543 * into JSON 544 */ 545 template <typename CallbackFunc> 546 inline void getLDAPConfigData(const std::string& ldapType, 547 CallbackFunc&& callback) 548 { 549 constexpr std::array<std::string_view, 2> interfaces = { 550 ldapEnableInterface, ldapConfigInterface}; 551 552 dbus::utility::getDbusObject( 553 ldapConfigObjectName, interfaces, 554 [callback = std::forward<CallbackFunc>(callback), 555 ldapType](const boost::system::error_code& ec, 556 const dbus::utility::MapperGetObject& resp) mutable { 557 if (ec || resp.empty()) 558 { 559 BMCWEB_LOG_WARNING( 560 "DBUS response error during getting of service name: {}", 561 ec); 562 LDAPConfigData empty{}; 563 callback(false, empty, ldapType); 564 return; 565 } 566 std::string service = resp.begin()->first; 567 sdbusplus::message::object_path path(ldapRootObject); 568 dbus::utility::getManagedObjects( 569 service, path, 570 [callback, ldapType](const boost::system::error_code& ec2, 571 const dbus::utility::ManagedObjectType& 572 ldapObjects) mutable { 573 LDAPConfigData confData{}; 574 if (ec2) 575 { 576 callback(false, confData, ldapType); 577 BMCWEB_LOG_WARNING("D-Bus responses error: {}", ec2); 578 return; 579 } 580 581 std::string ldapDbusType; 582 std::string searchString; 583 584 if (ldapType == "LDAP") 585 { 586 ldapDbusType = 587 "xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap"; 588 searchString = "openldap"; 589 } 590 else if (ldapType == "ActiveDirectory") 591 { 592 ldapDbusType = 593 "xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory"; 594 searchString = "active_directory"; 595 } 596 else 597 { 598 BMCWEB_LOG_ERROR( 599 "Can't get the DbusType for the given type={}", 600 ldapType); 601 callback(false, confData, ldapType); 602 return; 603 } 604 605 std::string ldapEnableInterfaceStr = ldapEnableInterface; 606 std::string ldapConfigInterfaceStr = ldapConfigInterface; 607 608 for (const auto& object : ldapObjects) 609 { 610 // let's find the object whose ldap type is equal to the 611 // given type 612 if (object.first.str.find(searchString) == 613 std::string::npos) 614 { 615 continue; 616 } 617 618 for (const auto& interface : object.second) 619 { 620 if (interface.first == ldapEnableInterfaceStr) 621 { 622 // rest of the properties are string. 623 for (const auto& property : interface.second) 624 { 625 if (property.first == "Enabled") 626 { 627 const bool* value = 628 std::get_if<bool>(&property.second); 629 if (value == nullptr) 630 { 631 continue; 632 } 633 confData.serviceEnabled = *value; 634 break; 635 } 636 } 637 } 638 else if (interface.first == ldapConfigInterfaceStr) 639 { 640 for (const auto& property : interface.second) 641 { 642 const std::string* strValue = 643 std::get_if<std::string>( 644 &property.second); 645 if (strValue == nullptr) 646 { 647 continue; 648 } 649 if (property.first == "LDAPServerURI") 650 { 651 confData.uri = *strValue; 652 } 653 else if (property.first == "LDAPBindDN") 654 { 655 confData.bindDN = *strValue; 656 } 657 else if (property.first == "LDAPBaseDN") 658 { 659 confData.baseDN = *strValue; 660 } 661 else if (property.first == 662 "LDAPSearchScope") 663 { 664 confData.searchScope = *strValue; 665 } 666 else if (property.first == 667 "GroupNameAttribute") 668 { 669 confData.groupAttribute = *strValue; 670 } 671 else if (property.first == 672 "UserNameAttribute") 673 { 674 confData.userNameAttribute = *strValue; 675 } 676 else if (property.first == "LDAPType") 677 { 678 confData.serverType = *strValue; 679 } 680 } 681 } 682 else if ( 683 interface.first == 684 "xyz.openbmc_project.User.PrivilegeMapperEntry") 685 { 686 LDAPRoleMapData roleMapData{}; 687 for (const auto& property : interface.second) 688 { 689 const std::string* strValue = 690 std::get_if<std::string>( 691 &property.second); 692 693 if (strValue == nullptr) 694 { 695 continue; 696 } 697 698 if (property.first == "GroupName") 699 { 700 roleMapData.groupName = *strValue; 701 } 702 else if (property.first == "Privilege") 703 { 704 roleMapData.privilege = *strValue; 705 } 706 } 707 708 confData.groupRoleList.emplace_back( 709 object.first.str, roleMapData); 710 } 711 } 712 } 713 callback(true, confData, ldapType); 714 }); 715 }); 716 } 717 718 /** 719 * @brief updates the LDAP server address and updates the 720 json response with the new value. 721 * @param serviceAddressList address to be updated. 722 * @param asyncResp pointer to the JSON response 723 * @param ldapServerElementName Type of LDAP 724 server(openLDAP/ActiveDirectory) 725 */ 726 727 inline void handleServiceAddressPatch( 728 const std::vector<std::string>& serviceAddressList, 729 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 730 const std::string& ldapServerElementName, 731 const std::string& ldapConfigObject) 732 { 733 setDbusProperty(asyncResp, ldapServerElementName + "/ServiceAddress", 734 ldapDbusService, ldapConfigObject, ldapConfigInterface, 735 "LDAPServerURI", serviceAddressList.front()); 736 } 737 /** 738 * @brief updates the LDAP Bind DN and updates the 739 json response with the new value. 740 * @param username name of the user which needs to be updated. 741 * @param asyncResp pointer to the JSON response 742 * @param ldapServerElementName Type of LDAP 743 server(openLDAP/ActiveDirectory) 744 */ 745 746 inline void handleUserNamePatch( 747 const std::string& username, 748 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 749 const std::string& ldapServerElementName, 750 const std::string& ldapConfigObject) 751 { 752 setDbusProperty(asyncResp, 753 ldapServerElementName + "/Authentication/Username", 754 ldapDbusService, ldapConfigObject, ldapConfigInterface, 755 "LDAPBindDN", username); 756 } 757 758 /** 759 * @brief updates the LDAP password 760 * @param password : ldap password which needs to be updated. 761 * @param asyncResp pointer to the JSON response 762 * @param ldapServerElementName Type of LDAP 763 * server(openLDAP/ActiveDirectory) 764 */ 765 766 inline void handlePasswordPatch( 767 const std::string& password, 768 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 769 const std::string& ldapServerElementName, 770 const std::string& ldapConfigObject) 771 { 772 setDbusProperty(asyncResp, 773 ldapServerElementName + "/Authentication/Password", 774 ldapDbusService, ldapConfigObject, ldapConfigInterface, 775 "LDAPBindDNPassword", password); 776 } 777 778 /** 779 * @brief updates the LDAP BaseDN and updates the 780 json response with the new value. 781 * @param baseDNList baseDN list which needs to be updated. 782 * @param asyncResp pointer to the JSON response 783 * @param ldapServerElementName Type of LDAP 784 server(openLDAP/ActiveDirectory) 785 */ 786 787 inline void handleBaseDNPatch( 788 const std::vector<std::string>& baseDNList, 789 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 790 const std::string& ldapServerElementName, 791 const std::string& ldapConfigObject) 792 { 793 setDbusProperty(asyncResp, 794 ldapServerElementName + 795 "/LDAPService/SearchSettings/BaseDistinguishedNames", 796 ldapDbusService, ldapConfigObject, ldapConfigInterface, 797 "LDAPBaseDN", baseDNList.front()); 798 } 799 /** 800 * @brief updates the LDAP user name attribute and updates the 801 json response with the new value. 802 * @param userNameAttribute attribute to be updated. 803 * @param asyncResp pointer to the JSON response 804 * @param ldapServerElementName Type of LDAP 805 server(openLDAP/ActiveDirectory) 806 */ 807 808 inline void handleUserNameAttrPatch( 809 const std::string& userNameAttribute, 810 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 811 const std::string& ldapServerElementName, 812 const std::string& ldapConfigObject) 813 { 814 setDbusProperty( 815 asyncResp, 816 ldapServerElementName + "LDAPService/SearchSettings/UsernameAttribute", 817 ldapDbusService, ldapConfigObject, ldapConfigInterface, 818 "UserNameAttribute", userNameAttribute); 819 } 820 /** 821 * @brief updates the LDAP group attribute and updates the 822 json response with the new value. 823 * @param groupsAttribute attribute to be updated. 824 * @param asyncResp pointer to the JSON response 825 * @param ldapServerElementName Type of LDAP 826 server(openLDAP/ActiveDirectory) 827 */ 828 829 inline void handleGroupNameAttrPatch( 830 const std::string& groupsAttribute, 831 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 832 const std::string& ldapServerElementName, 833 const std::string& ldapConfigObject) 834 { 835 setDbusProperty( 836 asyncResp, 837 ldapServerElementName + "/LDAPService/SearchSettings/GroupsAttribute", 838 ldapDbusService, ldapConfigObject, ldapConfigInterface, 839 "GroupNameAttribute", groupsAttribute); 840 } 841 /** 842 * @brief updates the LDAP service enable and updates the 843 json response with the new value. 844 * @param input JSON data. 845 * @param asyncResp pointer to the JSON response 846 * @param ldapServerElementName Type of LDAP 847 server(openLDAP/ActiveDirectory) 848 */ 849 850 inline void handleServiceEnablePatch( 851 bool serviceEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 852 const std::string& ldapServerElementName, 853 const std::string& ldapConfigObject) 854 { 855 setDbusProperty(asyncResp, ldapServerElementName + "/ServiceEnabled", 856 ldapDbusService, ldapConfigObject, ldapEnableInterface, 857 "Enabled", serviceEnabled); 858 } 859 860 struct AuthMethods 861 { 862 std::optional<bool> basicAuth; 863 std::optional<bool> cookie; 864 std::optional<bool> sessionToken; 865 std::optional<bool> xToken; 866 std::optional<bool> tls; 867 }; 868 869 inline void handleAuthMethodsPatch( 870 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 871 const AuthMethods& auth) 872 { 873 persistent_data::AuthConfigMethods& authMethodsConfig = 874 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 875 876 if (auth.basicAuth) 877 { 878 if constexpr (!BMCWEB_BASIC_AUTH) 879 { 880 messages::actionNotSupported( 881 asyncResp->res, 882 "Setting BasicAuth when basic-auth feature is disabled"); 883 return; 884 } 885 886 authMethodsConfig.basic = *auth.basicAuth; 887 } 888 889 if (auth.cookie) 890 { 891 if constexpr (!BMCWEB_COOKIE_AUTH) 892 { 893 messages::actionNotSupported( 894 asyncResp->res, 895 "Setting Cookie when cookie-auth feature is disabled"); 896 return; 897 } 898 authMethodsConfig.cookie = *auth.cookie; 899 } 900 901 if (auth.sessionToken) 902 { 903 if constexpr (!BMCWEB_SESSION_AUTH) 904 { 905 messages::actionNotSupported( 906 asyncResp->res, 907 "Setting SessionToken when session-auth feature is disabled"); 908 return; 909 } 910 authMethodsConfig.sessionToken = *auth.sessionToken; 911 } 912 913 if (auth.xToken) 914 { 915 if constexpr (!BMCWEB_XTOKEN_AUTH) 916 { 917 messages::actionNotSupported( 918 asyncResp->res, 919 "Setting XToken when xtoken-auth feature is disabled"); 920 return; 921 } 922 authMethodsConfig.xtoken = *auth.xToken; 923 } 924 925 if (auth.tls) 926 { 927 if constexpr (!BMCWEB_MUTUAL_TLS_AUTH) 928 { 929 messages::actionNotSupported( 930 asyncResp->res, 931 "Setting TLS when mutual-tls-auth feature is disabled"); 932 return; 933 } 934 authMethodsConfig.tls = *auth.tls; 935 } 936 937 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 938 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken && 939 !authMethodsConfig.tls) 940 { 941 // Do not allow user to disable everything 942 messages::actionNotSupported(asyncResp->res, 943 "of disabling all available methods"); 944 return; 945 } 946 947 persistent_data::SessionStore::getInstance().updateAuthMethodsConfig( 948 authMethodsConfig); 949 // Save configuration immediately 950 persistent_data::getConfig().writeData(); 951 952 messages::success(asyncResp->res); 953 } 954 955 /** 956 * @brief Get the required values from the given JSON, validates the 957 * value and create the LDAP config object. 958 * @param input JSON data 959 * @param asyncResp pointer to the JSON response 960 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 961 */ 962 963 struct LdapPatchParams 964 { 965 std::optional<std::string> authType; 966 std::optional<std::vector<std::string>> serviceAddressList; 967 std::optional<bool> serviceEnabled; 968 std::optional<std::vector<std::string>> baseDNList; 969 std::optional<std::string> userNameAttribute; 970 std::optional<std::string> groupsAttribute; 971 std::optional<std::string> userName; 972 std::optional<std::string> password; 973 std::optional< 974 std::vector<std::variant<nlohmann::json::object_t, std::nullptr_t>>> 975 remoteRoleMapData; 976 }; 977 978 inline void handleLDAPPatch(LdapPatchParams&& input, 979 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 980 const std::string& serverType) 981 { 982 std::string dbusObjectPath; 983 if (serverType == "ActiveDirectory") 984 { 985 dbusObjectPath = adConfigObject; 986 } 987 else if (serverType == "LDAP") 988 { 989 dbusObjectPath = ldapConfigObjectName; 990 } 991 else 992 { 993 BMCWEB_LOG_ERROR("serverType wasn't AD or LDAP but was {}????", 994 serverType); 995 return; 996 } 997 998 if (input.authType && *input.authType != "UsernameAndPassword") 999 { 1000 messages::propertyValueNotInList(asyncResp->res, *input.authType, 1001 "AuthenticationType"); 1002 return; 1003 } 1004 1005 if (input.serviceAddressList) 1006 { 1007 if (input.serviceAddressList->empty()) 1008 { 1009 messages::propertyValueNotInList( 1010 asyncResp->res, *input.serviceAddressList, "ServiceAddress"); 1011 return; 1012 } 1013 } 1014 if (input.baseDNList) 1015 { 1016 if (input.baseDNList->empty()) 1017 { 1018 messages::propertyValueNotInList(asyncResp->res, *input.baseDNList, 1019 "BaseDistinguishedNames"); 1020 return; 1021 } 1022 } 1023 1024 // nothing to update, then return 1025 if (!input.userName && !input.password && !input.serviceAddressList && 1026 !input.baseDNList && !input.userNameAttribute && 1027 !input.groupsAttribute && !input.serviceEnabled && 1028 !input.remoteRoleMapData) 1029 { 1030 return; 1031 } 1032 1033 // Get the existing resource first then keep modifying 1034 // whenever any property gets updated. 1035 getLDAPConfigData(serverType, [asyncResp, input = std::move(input), 1036 dbusObjectPath = std::move(dbusObjectPath)]( 1037 bool success, 1038 const LDAPConfigData& confData, 1039 const std::string& serverT) mutable { 1040 if (!success) 1041 { 1042 messages::internalError(asyncResp->res); 1043 return; 1044 } 1045 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverT); 1046 if (confData.serviceEnabled) 1047 { 1048 // Disable the service first and update the rest of 1049 // the properties. 1050 handleServiceEnablePatch(false, asyncResp, serverT, dbusObjectPath); 1051 } 1052 1053 if (input.serviceAddressList) 1054 { 1055 handleServiceAddressPatch(*input.serviceAddressList, asyncResp, 1056 serverT, dbusObjectPath); 1057 } 1058 if (input.userName) 1059 { 1060 handleUserNamePatch(*input.userName, asyncResp, serverT, 1061 dbusObjectPath); 1062 } 1063 if (input.password) 1064 { 1065 handlePasswordPatch(*input.password, asyncResp, serverT, 1066 dbusObjectPath); 1067 } 1068 1069 if (input.baseDNList) 1070 { 1071 handleBaseDNPatch(*input.baseDNList, asyncResp, serverT, 1072 dbusObjectPath); 1073 } 1074 if (input.userNameAttribute) 1075 { 1076 handleUserNameAttrPatch(*input.userNameAttribute, asyncResp, 1077 serverT, dbusObjectPath); 1078 } 1079 if (input.groupsAttribute) 1080 { 1081 handleGroupNameAttrPatch(*input.groupsAttribute, asyncResp, serverT, 1082 dbusObjectPath); 1083 } 1084 if (input.serviceEnabled) 1085 { 1086 // if user has given the value as true then enable 1087 // the service. if user has given false then no-op 1088 // as service is already stopped. 1089 if (*input.serviceEnabled) 1090 { 1091 handleServiceEnablePatch(*input.serviceEnabled, asyncResp, 1092 serverT, dbusObjectPath); 1093 } 1094 } 1095 else 1096 { 1097 // if user has not given the service enabled value 1098 // then revert it to the same state as it was 1099 // before. 1100 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1101 serverT, dbusObjectPath); 1102 } 1103 1104 if (input.remoteRoleMapData) 1105 { 1106 handleRoleMapPatch(asyncResp, confData.groupRoleList, serverT, 1107 *input.remoteRoleMapData); 1108 } 1109 }); 1110 } 1111 1112 struct UserUpdateParams 1113 { 1114 std::string username; 1115 std::optional<std::string> password; 1116 std::optional<bool> enabled; 1117 std::optional<std::string> roleId; 1118 std::optional<bool> locked; 1119 std::optional<std::vector<std::string>> accountTypes; 1120 bool userSelf; 1121 std::shared_ptr<persistent_data::UserSession> session; 1122 std::string dbusObjectPath; 1123 }; 1124 1125 inline void afterVerifyUserExists( 1126 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1127 const UserUpdateParams& params, int rc) 1128 { 1129 if (rc <= 0) 1130 { 1131 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1132 params.username); 1133 return; 1134 } 1135 1136 if (params.password) 1137 { 1138 int retval = pamUpdatePassword(params.username, *params.password); 1139 1140 if (retval == PAM_USER_UNKNOWN) 1141 { 1142 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1143 params.username); 1144 } 1145 else if (retval == PAM_AUTHTOK_ERR) 1146 { 1147 // If password is invalid 1148 messages::propertyValueFormatError(asyncResp->res, nullptr, 1149 "Password"); 1150 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1151 } 1152 else if (retval != PAM_SUCCESS) 1153 { 1154 messages::internalError(asyncResp->res); 1155 return; 1156 } 1157 else 1158 { 1159 // Remove existing sessions of the user when password 1160 // changed 1161 persistent_data::SessionStore::getInstance() 1162 .removeSessionsByUsernameExceptSession(params.username, 1163 params.session); 1164 messages::success(asyncResp->res); 1165 } 1166 } 1167 1168 if (params.enabled) 1169 { 1170 setDbusProperty( 1171 asyncResp, "Enabled", "xyz.openbmc_project.User.Manager", 1172 params.dbusObjectPath, "xyz.openbmc_project.User.Attributes", 1173 "UserEnabled", *params.enabled); 1174 } 1175 1176 if (params.roleId) 1177 { 1178 std::string priv = getPrivilegeFromRoleId(*params.roleId); 1179 if (priv.empty()) 1180 { 1181 messages::propertyValueNotInList(asyncResp->res, true, "Locked"); 1182 return; 1183 } 1184 setDbusProperty(asyncResp, "RoleId", "xyz.openbmc_project.User.Manager", 1185 params.dbusObjectPath, 1186 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1187 priv); 1188 } 1189 1190 if (params.locked) 1191 { 1192 // admin can unlock the account which is locked by 1193 // successive authentication failures but admin should 1194 // not be allowed to lock an account. 1195 if (*params.locked) 1196 { 1197 messages::propertyValueNotInList(asyncResp->res, "true", "Locked"); 1198 return; 1199 } 1200 setDbusProperty(asyncResp, "Locked", "xyz.openbmc_project.User.Manager", 1201 params.dbusObjectPath, 1202 "xyz.openbmc_project.User.Attributes", 1203 "UserLockedForFailedAttempt", *params.locked); 1204 } 1205 1206 if (params.accountTypes) 1207 { 1208 patchAccountTypes(*params.accountTypes, asyncResp, 1209 params.dbusObjectPath, params.userSelf); 1210 } 1211 } 1212 1213 inline void updateUserProperties( 1214 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1215 const std::string& username, const std::optional<std::string>& password, 1216 const std::optional<bool>& enabled, 1217 const std::optional<std::string>& roleId, const std::optional<bool>& locked, 1218 const std::optional<std::vector<std::string>>& accountTypes, bool userSelf, 1219 const std::shared_ptr<persistent_data::UserSession>& session) 1220 { 1221 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1222 tempObjPath /= username; 1223 std::string dbusObjectPath(tempObjPath); 1224 1225 UserUpdateParams params{username, password, enabled, 1226 roleId, locked, accountTypes, 1227 userSelf, session, dbusObjectPath}; 1228 1229 dbus::utility::checkDbusPathExists( 1230 dbusObjectPath, 1231 std::bind_front(afterVerifyUserExists, asyncResp, std::move(params))); 1232 } 1233 1234 inline void handleAccountServiceHead( 1235 App& app, const crow::Request& req, 1236 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1237 { 1238 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1239 { 1240 return; 1241 } 1242 asyncResp->res.addHeader( 1243 boost::beast::http::field::link, 1244 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1245 } 1246 1247 inline void getClientCertificates( 1248 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1249 const nlohmann::json::json_pointer& keyLocation) 1250 { 1251 boost::urls::url url( 1252 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates"); 1253 std::array<std::string_view, 1> interfaces = { 1254 "xyz.openbmc_project.Certs.Certificate"}; 1255 std::string path = "/xyz/openbmc_project/certs/authority/truststore"; 1256 1257 collection_util::getCollectionToKey(asyncResp, url, interfaces, path, 1258 keyLocation); 1259 } 1260 1261 inline void handleAccountServiceClientCertificatesInstanceHead( 1262 App& app, const crow::Request& req, 1263 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1264 const std::string& /*id*/) 1265 { 1266 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1267 { 1268 return; 1269 } 1270 1271 asyncResp->res.addHeader( 1272 boost::beast::http::field::link, 1273 "</redfish/v1/JsonSchemas/Certificate/Certificate.json>; rel=describedby"); 1274 } 1275 1276 inline void handleAccountServiceClientCertificatesInstanceGet( 1277 App& app, const crow::Request& req, 1278 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1279 { 1280 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1281 { 1282 return; 1283 } 1284 BMCWEB_LOG_DEBUG("ClientCertificate Certificate ID={}", id); 1285 const boost::urls::url certURL = boost::urls::format( 1286 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/{}", 1287 id); 1288 std::string objPath = 1289 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1290 getCertificateProperties( 1291 asyncResp, objPath, 1292 "xyz.openbmc_project.Certs.Manager.Authority.Truststore", id, certURL, 1293 "Client Certificate"); 1294 } 1295 1296 inline void handleAccountServiceClientCertificatesHead( 1297 App& app, const crow::Request& req, 1298 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1299 { 1300 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1301 { 1302 return; 1303 } 1304 1305 asyncResp->res.addHeader( 1306 boost::beast::http::field::link, 1307 "</redfish/v1/JsonSchemas/CertificateCollection/CertificateCollection.json>; rel=describedby"); 1308 } 1309 1310 inline void handleAccountServiceClientCertificatesGet( 1311 App& app, const crow::Request& req, 1312 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1313 { 1314 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1315 { 1316 return; 1317 } 1318 1319 nlohmann::json& json = asyncResp->res.jsonValue; 1320 json["@odata.id"] = 1321 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates"; 1322 json["@odata.type"] = "#CertificateCollection.CertificateCollection"; 1323 json["Name"] = "Certificates Collection"; 1324 json["Description"] = "Multi-factor Authentication Client Certificates"; 1325 getClientCertificates(asyncResp, "/Members"_json_pointer); 1326 } 1327 1328 using account_service::CertificateMappingAttribute; 1329 using persistent_data::MTLSCommonNameParseMode; 1330 inline CertificateMappingAttribute getCertificateMapping( 1331 MTLSCommonNameParseMode parse) 1332 { 1333 switch (parse) 1334 { 1335 case MTLSCommonNameParseMode::CommonName: 1336 { 1337 return CertificateMappingAttribute::CommonName; 1338 } 1339 break; 1340 case MTLSCommonNameParseMode::Whole: 1341 { 1342 return CertificateMappingAttribute::Whole; 1343 } 1344 break; 1345 case MTLSCommonNameParseMode::UserPrincipalName: 1346 { 1347 return CertificateMappingAttribute::UserPrincipalName; 1348 } 1349 break; 1350 default: 1351 { 1352 return CertificateMappingAttribute::Invalid; 1353 } 1354 break; 1355 } 1356 } 1357 1358 inline void handleAccountServiceGet( 1359 App& app, const crow::Request& req, 1360 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1361 { 1362 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1363 { 1364 return; 1365 } 1366 1367 if (req.session == nullptr) 1368 { 1369 messages::internalError(asyncResp->res); 1370 return; 1371 } 1372 1373 const persistent_data::AuthConfigMethods& authMethodsConfig = 1374 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1375 1376 asyncResp->res.addHeader( 1377 boost::beast::http::field::link, 1378 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1379 1380 nlohmann::json& json = asyncResp->res.jsonValue; 1381 json["@odata.id"] = "/redfish/v1/AccountService"; 1382 json["@odata.type"] = "#AccountService.v1_15_0.AccountService"; 1383 json["Id"] = "AccountService"; 1384 json["Name"] = "Account Service"; 1385 json["Description"] = "Account Service"; 1386 json["ServiceEnabled"] = true; 1387 json["MaxPasswordLength"] = 20; 1388 json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; 1389 json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; 1390 json["HTTPBasicAuth"] = authMethodsConfig.basic 1391 ? account_service::BasicAuthState::Enabled 1392 : account_service::BasicAuthState::Disabled; 1393 1394 nlohmann::json::array_t allowed; 1395 allowed.emplace_back(account_service::BasicAuthState::Enabled); 1396 allowed.emplace_back(account_service::BasicAuthState::Disabled); 1397 json["HTTPBasicAuth@AllowableValues"] = std::move(allowed); 1398 1399 nlohmann::json::object_t clientCertificate; 1400 clientCertificate["Enabled"] = authMethodsConfig.tls; 1401 clientCertificate["RespondToUnauthenticatedClients"] = 1402 !authMethodsConfig.tlsStrict; 1403 1404 using account_service::CertificateMappingAttribute; 1405 1406 CertificateMappingAttribute mapping = 1407 getCertificateMapping(authMethodsConfig.mTLSCommonNameParsingMode); 1408 if (mapping == CertificateMappingAttribute::Invalid) 1409 { 1410 messages::internalError(asyncResp->res); 1411 } 1412 else 1413 { 1414 clientCertificate["CertificateMappingAttribute"] = mapping; 1415 } 1416 nlohmann::json::object_t certificates; 1417 certificates["@odata.id"] = 1418 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates"; 1419 certificates["@odata.type"] = 1420 "#CertificateCollection.CertificateCollection"; 1421 clientCertificate["Certificates"] = std::move(certificates); 1422 json["MultiFactorAuth"]["ClientCertificate"] = std::move(clientCertificate); 1423 1424 getClientCertificates( 1425 asyncResp, 1426 "/MultiFactorAuth/ClientCertificate/Certificates/Members"_json_pointer); 1427 1428 json["Oem"]["OpenBMC"]["@odata.type"] = 1429 "#OpenBMCAccountService.v1_0_0.AccountService"; 1430 json["Oem"]["OpenBMC"]["@odata.id"] = 1431 "/redfish/v1/AccountService#/Oem/OpenBMC"; 1432 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = 1433 authMethodsConfig.basic; 1434 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = 1435 authMethodsConfig.sessionToken; 1436 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; 1437 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; 1438 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; 1439 1440 // /redfish/v1/AccountService/LDAP/Certificates is something only 1441 // ConfigureManager can access then only display when the user has 1442 // permissions ConfigureManager 1443 Privileges effectiveUserPrivileges = 1444 redfish::getUserPrivileges(*req.session); 1445 1446 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 1447 effectiveUserPrivileges)) 1448 { 1449 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = 1450 "/redfish/v1/AccountService/LDAP/Certificates"; 1451 } 1452 dbus::utility::getAllProperties( 1453 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1454 "xyz.openbmc_project.User.AccountPolicy", 1455 [asyncResp](const boost::system::error_code& ec, 1456 const dbus::utility::DBusPropertiesMap& propertiesList) { 1457 if (ec) 1458 { 1459 messages::internalError(asyncResp->res); 1460 return; 1461 } 1462 1463 BMCWEB_LOG_DEBUG("Got {} properties for AccountService", 1464 propertiesList.size()); 1465 1466 const uint8_t* minPasswordLength = nullptr; 1467 const uint32_t* accountUnlockTimeout = nullptr; 1468 const uint16_t* maxLoginAttemptBeforeLockout = nullptr; 1469 1470 const bool success = sdbusplus::unpackPropertiesNoThrow( 1471 dbus_utils::UnpackErrorPrinter(), propertiesList, 1472 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout", 1473 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout", 1474 maxLoginAttemptBeforeLockout); 1475 1476 if (!success) 1477 { 1478 messages::internalError(asyncResp->res); 1479 return; 1480 } 1481 1482 if (minPasswordLength != nullptr) 1483 { 1484 asyncResp->res.jsonValue["MinPasswordLength"] = 1485 *minPasswordLength; 1486 } 1487 1488 if (accountUnlockTimeout != nullptr) 1489 { 1490 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1491 *accountUnlockTimeout; 1492 } 1493 1494 if (maxLoginAttemptBeforeLockout != nullptr) 1495 { 1496 asyncResp->res.jsonValue["AccountLockoutThreshold"] = 1497 *maxLoginAttemptBeforeLockout; 1498 } 1499 }); 1500 1501 auto callback = [asyncResp](bool success, const LDAPConfigData& confData, 1502 const std::string& ldapType) { 1503 if (!success) 1504 { 1505 return; 1506 } 1507 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1508 }; 1509 1510 getLDAPConfigData("LDAP", callback); 1511 getLDAPConfigData("ActiveDirectory", callback); 1512 } 1513 1514 inline void handleCertificateMappingAttributePatch( 1515 crow::Response& res, const std::string& certMapAttribute) 1516 { 1517 MTLSCommonNameParseMode parseMode = 1518 persistent_data::getMTLSCommonNameParseMode(certMapAttribute); 1519 if (parseMode == MTLSCommonNameParseMode::Invalid) 1520 { 1521 messages::propertyValueNotInList(res, "CertificateMappingAttribute", 1522 certMapAttribute); 1523 return; 1524 } 1525 1526 persistent_data::AuthConfigMethods& authMethodsConfig = 1527 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1528 authMethodsConfig.mTLSCommonNameParsingMode = parseMode; 1529 } 1530 1531 inline void handleRespondToUnauthenticatedClientsPatch( 1532 App& app, const crow::Request& req, crow::Response& res, 1533 bool respondToUnauthenticatedClients) 1534 { 1535 if (req.session != nullptr) 1536 { 1537 // Sanity check. If the user isn't currently authenticated with mutual 1538 // TLS, they very likely are about to permanently lock themselves out. 1539 // Make sure they're using mutual TLS before allowing locking. 1540 if (req.session->sessionType != persistent_data::SessionType::MutualTLS) 1541 { 1542 messages::propertyValueExternalConflict( 1543 res, 1544 "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients", 1545 respondToUnauthenticatedClients); 1546 return; 1547 } 1548 } 1549 1550 persistent_data::AuthConfigMethods& authMethodsConfig = 1551 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1552 1553 // Change the settings 1554 authMethodsConfig.tlsStrict = !respondToUnauthenticatedClients; 1555 1556 // Write settings to disk 1557 persistent_data::getConfig().writeData(); 1558 1559 // Trigger a reload, to apply the new settings to new connections 1560 app.loadCertificate(); 1561 } 1562 1563 inline void handleAccountServicePatch( 1564 App& app, const crow::Request& req, 1565 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1566 { 1567 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1568 { 1569 return; 1570 } 1571 std::optional<uint32_t> unlockTimeout; 1572 std::optional<uint16_t> lockoutThreshold; 1573 std::optional<uint8_t> minPasswordLength; 1574 std::optional<uint16_t> maxPasswordLength; 1575 LdapPatchParams ldapObject; 1576 std::optional<std::string> certificateMappingAttribute; 1577 std::optional<bool> respondToUnauthenticatedClients; 1578 LdapPatchParams activeDirectoryObject; 1579 AuthMethods auth; 1580 std::optional<std::string> httpBasicAuth; 1581 1582 if (!json_util::readJsonPatch( // 1583 req, asyncResp->res, // 1584 "AccountLockoutDuration", unlockTimeout, // 1585 "AccountLockoutThreshold", lockoutThreshold, // 1586 "ActiveDirectory/Authentication/AuthenticationType", 1587 activeDirectoryObject.authType, // 1588 "ActiveDirectory/Authentication/Password", 1589 activeDirectoryObject.password, // 1590 "ActiveDirectory/Authentication/Username", 1591 activeDirectoryObject.userName, // 1592 "ActiveDirectory/LDAPService/SearchSettings/BaseDistinguishedNames", 1593 activeDirectoryObject.baseDNList, // 1594 "ActiveDirectory/LDAPService/SearchSettings/GroupsAttribute", 1595 activeDirectoryObject.groupsAttribute, // 1596 "ActiveDirectory/LDAPService/SearchSettings/UsernameAttribute", 1597 activeDirectoryObject.userNameAttribute, // 1598 "ActiveDirectory/RemoteRoleMapping", 1599 activeDirectoryObject.remoteRoleMapData, // 1600 "ActiveDirectory/ServiceAddresses", 1601 activeDirectoryObject.serviceAddressList, // 1602 "ActiveDirectory/ServiceEnabled", 1603 activeDirectoryObject.serviceEnabled, // 1604 "HTTPBasicAuth", httpBasicAuth, // 1605 "LDAP/Authentication/AuthenticationType", ldapObject.authType, // 1606 "LDAP/Authentication/Password", ldapObject.password, // 1607 "LDAP/Authentication/Username", ldapObject.userName, // 1608 "LDAP/LDAPService/SearchSettings/BaseDistinguishedNames", 1609 ldapObject.baseDNList, // 1610 "LDAP/LDAPService/SearchSettings/GroupsAttribute", 1611 ldapObject.groupsAttribute, // 1612 "LDAP/LDAPService/SearchSettings/UsernameAttribute", 1613 ldapObject.userNameAttribute, // 1614 "LDAP/RemoteRoleMapping", ldapObject.remoteRoleMapData, // 1615 "LDAP/ServiceAddresses", ldapObject.serviceAddressList, // 1616 "LDAP/ServiceEnabled", ldapObject.serviceEnabled, // 1617 "MaxPasswordLength", maxPasswordLength, // 1618 "MinPasswordLength", minPasswordLength, // 1619 "MultiFactorAuth/ClientCertificate/CertificateMappingAttribute", 1620 certificateMappingAttribute, // 1621 "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients", 1622 respondToUnauthenticatedClients, // 1623 "Oem/OpenBMC/AuthMethods/BasicAuth", auth.basicAuth, // 1624 "Oem/OpenBMC/AuthMethods/Cookie", auth.cookie, // 1625 "Oem/OpenBMC/AuthMethods/SessionToken", auth.sessionToken, // 1626 "Oem/OpenBMC/AuthMethods/TLS", auth.tls, // 1627 "Oem/OpenBMC/AuthMethods/XToken", auth.xToken // 1628 )) 1629 { 1630 return; 1631 } 1632 1633 if (httpBasicAuth) 1634 { 1635 if (*httpBasicAuth == "Enabled") 1636 { 1637 auth.basicAuth = true; 1638 } 1639 else if (*httpBasicAuth == "Disabled") 1640 { 1641 auth.basicAuth = false; 1642 } 1643 else 1644 { 1645 messages::propertyValueNotInList(asyncResp->res, "HttpBasicAuth", 1646 *httpBasicAuth); 1647 } 1648 } 1649 1650 if (respondToUnauthenticatedClients) 1651 { 1652 handleRespondToUnauthenticatedClientsPatch( 1653 app, req, asyncResp->res, *respondToUnauthenticatedClients); 1654 } 1655 1656 if (certificateMappingAttribute) 1657 { 1658 handleCertificateMappingAttributePatch(asyncResp->res, 1659 *certificateMappingAttribute); 1660 } 1661 1662 if (minPasswordLength) 1663 { 1664 setDbusProperty( 1665 asyncResp, "MinPasswordLength", "xyz.openbmc_project.User.Manager", 1666 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1667 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", 1668 *minPasswordLength); 1669 } 1670 1671 if (maxPasswordLength) 1672 { 1673 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1674 } 1675 1676 handleLDAPPatch(std::move(activeDirectoryObject), asyncResp, 1677 "ActiveDirectory"); 1678 handleLDAPPatch(std::move(ldapObject), asyncResp, "LDAP"); 1679 1680 handleAuthMethodsPatch(asyncResp, auth); 1681 1682 if (unlockTimeout) 1683 { 1684 setDbusProperty( 1685 asyncResp, "AccountLockoutDuration", 1686 "xyz.openbmc_project.User.Manager", 1687 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1688 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", 1689 *unlockTimeout); 1690 } 1691 if (lockoutThreshold) 1692 { 1693 setDbusProperty( 1694 asyncResp, "AccountLockoutThreshold", 1695 "xyz.openbmc_project.User.Manager", 1696 sdbusplus::message::object_path("/xyz/openbmc_project/user"), 1697 "xyz.openbmc_project.User.AccountPolicy", 1698 "MaxLoginAttemptBeforeLockout", *lockoutThreshold); 1699 } 1700 } 1701 1702 inline void handleAccountCollectionHead( 1703 App& app, const crow::Request& req, 1704 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1705 { 1706 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1707 { 1708 return; 1709 } 1710 asyncResp->res.addHeader( 1711 boost::beast::http::field::link, 1712 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1713 } 1714 1715 inline void handleAccountCollectionGet( 1716 App& app, const crow::Request& req, 1717 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1718 { 1719 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1720 { 1721 return; 1722 } 1723 1724 if (req.session == nullptr) 1725 { 1726 messages::internalError(asyncResp->res); 1727 return; 1728 } 1729 1730 asyncResp->res.addHeader( 1731 boost::beast::http::field::link, 1732 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1733 1734 asyncResp->res.jsonValue["@odata.id"] = 1735 "/redfish/v1/AccountService/Accounts"; 1736 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." 1737 "ManagerAccountCollection"; 1738 asyncResp->res.jsonValue["Name"] = "Accounts Collection"; 1739 asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; 1740 1741 Privileges effectiveUserPrivileges = 1742 redfish::getUserPrivileges(*req.session); 1743 1744 std::string thisUser; 1745 if (req.session) 1746 { 1747 thisUser = req.session->username; 1748 } 1749 sdbusplus::message::object_path path("/xyz/openbmc_project/user"); 1750 dbus::utility::getManagedObjects( 1751 "xyz.openbmc_project.User.Manager", path, 1752 [asyncResp, thisUser, effectiveUserPrivileges]( 1753 const boost::system::error_code& ec, 1754 const dbus::utility::ManagedObjectType& users) { 1755 if (ec) 1756 { 1757 messages::internalError(asyncResp->res); 1758 return; 1759 } 1760 1761 bool userCanSeeAllAccounts = 1762 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); 1763 1764 bool userCanSeeSelf = 1765 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); 1766 1767 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 1768 memberArray = nlohmann::json::array(); 1769 1770 for (const auto& userpath : users) 1771 { 1772 std::string user = userpath.first.filename(); 1773 if (user.empty()) 1774 { 1775 messages::internalError(asyncResp->res); 1776 BMCWEB_LOG_ERROR("Invalid firmware ID"); 1777 1778 return; 1779 } 1780 1781 // As clarified by Redfish here: 1782 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1783 // Users without ConfigureUsers, only see their own 1784 // account. Users with ConfigureUsers, see all 1785 // accounts. 1786 if (userCanSeeAllAccounts || 1787 (thisUser == user && userCanSeeSelf)) 1788 { 1789 nlohmann::json::object_t member; 1790 member["@odata.id"] = boost::urls::format( 1791 "/redfish/v1/AccountService/Accounts/{}", user); 1792 memberArray.emplace_back(std::move(member)); 1793 } 1794 } 1795 asyncResp->res.jsonValue["Members@odata.count"] = 1796 memberArray.size(); 1797 }); 1798 } 1799 1800 inline void processAfterCreateUser( 1801 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1802 const std::string& username, const std::string& password, 1803 const boost::system::error_code& ec, sdbusplus::message_t& m) 1804 { 1805 if (ec) 1806 { 1807 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); 1808 return; 1809 } 1810 1811 if (pamUpdatePassword(username, password) != PAM_SUCCESS) 1812 { 1813 // At this point we have a user that's been 1814 // created, but the password set 1815 // failed.Something is wrong, so delete the user 1816 // that we've already created 1817 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1818 tempObjPath /= username; 1819 const std::string userPath(tempObjPath); 1820 1821 dbus::utility::async_method_call( 1822 asyncResp, 1823 [asyncResp, password](const boost::system::error_code& ec3) { 1824 if (ec3) 1825 { 1826 messages::internalError(asyncResp->res); 1827 return; 1828 } 1829 1830 // If password is invalid 1831 messages::propertyValueFormatError(asyncResp->res, nullptr, 1832 "Password"); 1833 }, 1834 "xyz.openbmc_project.User.Manager", userPath, 1835 "xyz.openbmc_project.Object.Delete", "Delete"); 1836 1837 BMCWEB_LOG_ERROR("pamUpdatePassword Failed"); 1838 return; 1839 } 1840 1841 messages::created(asyncResp->res); 1842 asyncResp->res.addHeader("Location", 1843 "/redfish/v1/AccountService/Accounts/" + username); 1844 } 1845 1846 inline void processAfterGetAllGroups( 1847 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1848 const std::string& username, const std::string& password, 1849 const std::string& roleId, bool enabled, 1850 std::optional<std::vector<std::string>> accountTypes, 1851 const std::vector<std::string>& allGroupsList) 1852 { 1853 std::vector<std::string> userGroups; 1854 std::vector<std::string> accountTypeUserGroups; 1855 1856 // If user specified account types then convert them to unix user groups 1857 if (accountTypes) 1858 { 1859 if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes, 1860 accountTypeUserGroups)) 1861 { 1862 // Problem in mapping Account Types to User Groups, Error already 1863 // logged. 1864 return; 1865 } 1866 } 1867 1868 for (const auto& grp : allGroupsList) 1869 { 1870 // If user specified the account type then only accept groups which are 1871 // in the account types group list. 1872 if (!accountTypeUserGroups.empty()) 1873 { 1874 bool found = false; 1875 for (const auto& grp1 : accountTypeUserGroups) 1876 { 1877 if (grp == grp1) 1878 { 1879 found = true; 1880 break; 1881 } 1882 } 1883 if (!found) 1884 { 1885 continue; 1886 } 1887 } 1888 1889 // Console access is provided to the user who is a member of 1890 // hostconsole group and has a administrator role. So, set 1891 // hostconsole group only for the administrator. 1892 if ((grp == "hostconsole") && (roleId != "priv-admin")) 1893 { 1894 if (!accountTypeUserGroups.empty()) 1895 { 1896 BMCWEB_LOG_ERROR( 1897 "Only administrator can get HostConsole access"); 1898 asyncResp->res.result(boost::beast::http::status::bad_request); 1899 return; 1900 } 1901 continue; 1902 } 1903 userGroups.emplace_back(grp); 1904 } 1905 1906 // Make sure user specified groups are valid. This is internal error because 1907 // it some inconsistencies between user manager and bmcweb. 1908 if (!accountTypeUserGroups.empty() && 1909 accountTypeUserGroups.size() != userGroups.size()) 1910 { 1911 messages::internalError(asyncResp->res); 1912 return; 1913 } 1914 dbus::utility::async_method_call( 1915 asyncResp, 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 dbus::utility::async_method_call( 2195 asyncResp, 2196 [asyncResp, username](const boost::system::error_code& ec) { 2197 if (ec) 2198 { 2199 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 2200 username); 2201 return; 2202 } 2203 2204 messages::accountRemoved(asyncResp->res); 2205 }, 2206 "xyz.openbmc_project.User.Manager", userPath, 2207 "xyz.openbmc_project.Object.Delete", "Delete"); 2208 } 2209 2210 inline void handleAccountPatch( 2211 App& app, const crow::Request& req, 2212 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 2213 const std::string& username) 2214 { 2215 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 2216 { 2217 return; 2218 } 2219 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH) 2220 { 2221 // If authentication is disabled, there are no user accounts 2222 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 2223 return; 2224 } 2225 std::optional<std::string> newUserName; 2226 std::optional<std::string> password; 2227 std::optional<bool> enabled; 2228 std::optional<std::string> roleId; 2229 std::optional<bool> locked; 2230 std::optional<std::vector<std::string>> accountTypes; 2231 2232 if (req.session == nullptr) 2233 { 2234 messages::internalError(asyncResp->res); 2235 return; 2236 } 2237 2238 bool userSelf = (username == req.session->username); 2239 2240 Privileges effectiveUserPrivileges = 2241 redfish::getUserPrivileges(*req.session); 2242 Privileges configureUsers = {"ConfigureUsers"}; 2243 bool userHasConfigureUsers = 2244 effectiveUserPrivileges.isSupersetOf(configureUsers); 2245 if (userHasConfigureUsers) 2246 { 2247 // Users with ConfigureUsers can modify for all users 2248 if (!json_util::readJsonPatch( // 2249 req, asyncResp->res, // 2250 "AccountTypes", accountTypes, // 2251 "Enabled", enabled, // 2252 "Locked", locked, // 2253 "Password", password, // 2254 "RoleId", roleId, // 2255 "UserName", newUserName // 2256 )) 2257 { 2258 return; 2259 } 2260 } 2261 else 2262 { 2263 // ConfigureSelf accounts can only modify their own account 2264 if (!userSelf) 2265 { 2266 messages::insufficientPrivilege(asyncResp->res); 2267 return; 2268 } 2269 2270 // ConfigureSelf accounts can only modify their password 2271 if (!json_util::readJsonPatch(req, asyncResp->res, "Password", 2272 password)) 2273 { 2274 return; 2275 } 2276 } 2277 2278 // if user name is not provided in the patch method or if it 2279 // matches the user name in the URI, then we are treating it as 2280 // updating user properties other then username. If username 2281 // provided doesn't match the URI, then we are treating this as 2282 // user rename request. 2283 if (!newUserName || (newUserName.value() == username)) 2284 { 2285 updateUserProperties(asyncResp, username, password, enabled, roleId, 2286 locked, accountTypes, userSelf, req.session); 2287 return; 2288 } 2289 dbus::utility::async_method_call( 2290 asyncResp, 2291 [asyncResp, username, password(std::move(password)), 2292 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, 2293 locked, userSelf, session = req.session, 2294 accountTypes(std::move(accountTypes))]( 2295 const boost::system::error_code& ec, sdbusplus::message_t& m) { 2296 if (ec) 2297 { 2298 userErrorMessageHandler(m.get_error(), asyncResp, newUser, 2299 username); 2300 return; 2301 } 2302 2303 updateUserProperties(asyncResp, newUser, password, enabled, roleId, 2304 locked, accountTypes, userSelf, session); 2305 }, 2306 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 2307 "xyz.openbmc_project.User.Manager", "RenameUser", username, 2308 *newUserName); 2309 } 2310 2311 inline void requestAccountServiceRoutes(App& app) 2312 { 2313 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2314 .privileges(redfish::privileges::headAccountService) 2315 .methods(boost::beast::http::verb::head)( 2316 std::bind_front(handleAccountServiceHead, std::ref(app))); 2317 2318 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2319 .privileges(redfish::privileges::getAccountService) 2320 .methods(boost::beast::http::verb::get)( 2321 std::bind_front(handleAccountServiceGet, std::ref(app))); 2322 2323 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2324 .privileges(redfish::privileges::patchAccountService) 2325 .methods(boost::beast::http::verb::patch)( 2326 std::bind_front(handleAccountServicePatch, std::ref(app))); 2327 2328 BMCWEB_ROUTE( 2329 app, 2330 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/") 2331 .privileges(redfish::privileges::headCertificateCollection) 2332 .methods(boost::beast::http::verb::head)(std::bind_front( 2333 handleAccountServiceClientCertificatesHead, std::ref(app))); 2334 2335 BMCWEB_ROUTE( 2336 app, 2337 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/") 2338 .privileges(redfish::privileges::getCertificateCollection) 2339 .methods(boost::beast::http::verb::get)(std::bind_front( 2340 handleAccountServiceClientCertificatesGet, std::ref(app))); 2341 2342 BMCWEB_ROUTE( 2343 app, 2344 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/") 2345 .privileges(redfish::privileges::headCertificate) 2346 .methods(boost::beast::http::verb::head)(std::bind_front( 2347 handleAccountServiceClientCertificatesInstanceHead, std::ref(app))); 2348 2349 BMCWEB_ROUTE( 2350 app, 2351 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/") 2352 .privileges(redfish::privileges::getCertificate) 2353 .methods(boost::beast::http::verb::get)(std::bind_front( 2354 handleAccountServiceClientCertificatesInstanceGet, std::ref(app))); 2355 2356 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2357 .privileges(redfish::privileges::headManagerAccountCollection) 2358 .methods(boost::beast::http::verb::head)( 2359 std::bind_front(handleAccountCollectionHead, std::ref(app))); 2360 2361 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2362 .privileges(redfish::privileges::getManagerAccountCollection) 2363 .methods(boost::beast::http::verb::get)( 2364 std::bind_front(handleAccountCollectionGet, std::ref(app))); 2365 2366 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2367 .privileges(redfish::privileges::postManagerAccountCollection) 2368 .methods(boost::beast::http::verb::post)( 2369 std::bind_front(handleAccountCollectionPost, std::ref(app))); 2370 2371 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2372 .privileges(redfish::privileges::headManagerAccount) 2373 .methods(boost::beast::http::verb::head)( 2374 std::bind_front(handleAccountHead, std::ref(app))); 2375 2376 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2377 .privileges(redfish::privileges::getManagerAccount) 2378 .methods(boost::beast::http::verb::get)( 2379 std::bind_front(handleAccountGet, std::ref(app))); 2380 2381 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2382 // TODO this privilege should be using the generated endpoints, but 2383 // because of the special handling of ConfigureSelf, it's not able to 2384 // yet 2385 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) 2386 .methods(boost::beast::http::verb::patch)( 2387 std::bind_front(handleAccountPatch, std::ref(app))); 2388 2389 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2390 .privileges(redfish::privileges::deleteManagerAccount) 2391 .methods(boost::beast::http::verb::delete_)( 2392 std::bind_front(handleAccountDelete, std::ref(app))); 2393 } 2394 2395 } // namespace redfish 2396