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