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