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