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