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