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