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