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