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