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