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("HostConsole"); 139 accountTypes.emplace_back("ManagerConsole"); 140 } 141 else if (userGroup == "web") 142 { 143 // 'web' is one of the valid groups in the UserGroups property of 144 // the user account in the D-Bus object. This group is currently not 145 // doing anything, and is considered to be equivalent to 'redfish'. 146 // 'redfish' user group is mapped to 'Redfish'and 'WebUI' 147 // AccountTypes, so do nothing here... 148 } 149 else 150 { 151 // Invalid user group name. Caller throws an excption. 152 return false; 153 } 154 } 155 156 res.jsonValue["AccountTypes"] = std::move(accountTypes); 157 return true; 158 } 159 160 inline void userErrorMessageHandler( 161 const sd_bus_error* e, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 162 const std::string& newUser, const std::string& username) 163 { 164 if (e == nullptr) 165 { 166 messages::internalError(asyncResp->res); 167 return; 168 } 169 170 const char* errorMessage = e->name; 171 if (strcmp(errorMessage, 172 "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0) 173 { 174 messages::resourceAlreadyExists(asyncResp->res, "ManagerAccount", 175 "UserName", newUser); 176 } 177 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 178 "UserNameDoesNotExist") == 0) 179 { 180 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 181 } 182 else if ((strcmp(errorMessage, 183 "xyz.openbmc_project.Common.Error.InvalidArgument") == 184 0) || 185 (strcmp( 186 errorMessage, 187 "xyz.openbmc_project.User.Common.Error.UserNameGroupFail") == 188 0)) 189 { 190 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 191 } 192 else if (strcmp(errorMessage, 193 "xyz.openbmc_project.User.Common.Error.NoResource") == 0) 194 { 195 messages::createLimitReachedForResource(asyncResp->res); 196 } 197 else 198 { 199 messages::internalError(asyncResp->res); 200 } 201 } 202 203 inline void parseLDAPConfigData(nlohmann::json& jsonResponse, 204 const LDAPConfigData& confData, 205 const std::string& ldapType) 206 { 207 std::string service = 208 (ldapType == "LDAP") ? "LDAPService" : "ActiveDirectoryService"; 209 210 nlohmann::json& ldap = jsonResponse[ldapType]; 211 212 ldap["ServiceEnabled"] = confData.serviceEnabled; 213 ldap["ServiceAddresses"] = nlohmann::json::array({confData.uri}); 214 ldap["Authentication"]["AuthenticationType"] = 215 account_service::AuthenticationTypes::UsernameAndPassword; 216 ldap["Authentication"]["Username"] = confData.bindDN; 217 ldap["Authentication"]["Password"] = nullptr; 218 219 ldap["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] = 220 nlohmann::json::array({confData.baseDN}); 221 ldap["LDAPService"]["SearchSettings"]["UsernameAttribute"] = 222 confData.userNameAttribute; 223 ldap["LDAPService"]["SearchSettings"]["GroupsAttribute"] = 224 confData.groupAttribute; 225 226 nlohmann::json& roleMapArray = ldap["RemoteRoleMapping"]; 227 roleMapArray = nlohmann::json::array(); 228 for (const auto& obj : confData.groupRoleList) 229 { 230 BMCWEB_LOG_DEBUG << "Pushing the data groupName=" 231 << obj.second.groupName << "\n"; 232 233 nlohmann::json::object_t remoteGroup; 234 remoteGroup["RemoteGroup"] = obj.second.groupName; 235 remoteGroup["LocalRole"] = getRoleIdFromPrivilege(obj.second.privilege); 236 roleMapArray.emplace_back(std::move(remoteGroup)); 237 } 238 } 239 240 /** 241 * @brief validates given JSON input and then calls appropriate method to 242 * create, to delete or to set Rolemapping object based on the given input. 243 * 244 */ 245 inline void handleRoleMapPatch( 246 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 247 const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData, 248 const std::string& serverType, const std::vector<nlohmann::json>& input) 249 { 250 for (size_t index = 0; index < input.size(); index++) 251 { 252 const nlohmann::json& thisJson = input[index]; 253 254 if (thisJson.is_null()) 255 { 256 // delete the existing object 257 if (index < roleMapObjData.size()) 258 { 259 crow::connections::systemBus->async_method_call( 260 [asyncResp, roleMapObjData, serverType, 261 index](const boost::system::error_code ec) { 262 if (ec) 263 { 264 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 265 messages::internalError(asyncResp->res); 266 return; 267 } 268 asyncResp->res 269 .jsonValue[serverType]["RemoteRoleMapping"][index] = 270 nullptr; 271 }, 272 ldapDbusService, roleMapObjData[index].first, 273 "xyz.openbmc_project.Object.Delete", "Delete"); 274 } 275 else 276 { 277 BMCWEB_LOG_ERROR << "Can't delete the object"; 278 messages::propertyValueTypeError( 279 asyncResp->res, 280 thisJson.dump(2, ' ', true, 281 nlohmann::json::error_handler_t::replace), 282 "RemoteRoleMapping/" + std::to_string(index)); 283 return; 284 } 285 } 286 else if (thisJson.empty()) 287 { 288 // Don't do anything for the empty objects,parse next json 289 // eg {"RemoteRoleMapping",[{}]} 290 } 291 else 292 { 293 // update/create the object 294 std::optional<std::string> remoteGroup; 295 std::optional<std::string> localRole; 296 297 // This is a copy, but it's required in this case because of how 298 // readJson is structured 299 nlohmann::json thisJsonCopy = thisJson; 300 if (!json_util::readJson(thisJsonCopy, asyncResp->res, 301 "RemoteGroup", remoteGroup, "LocalRole", 302 localRole)) 303 { 304 continue; 305 } 306 307 // Update existing RoleMapping Object 308 if (index < roleMapObjData.size()) 309 { 310 BMCWEB_LOG_DEBUG << "Update Role Map Object"; 311 // If "RemoteGroup" info is provided 312 if (remoteGroup) 313 { 314 crow::connections::systemBus->async_method_call( 315 [asyncResp, roleMapObjData, serverType, index, 316 remoteGroup](const boost::system::error_code ec) { 317 if (ec) 318 { 319 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 320 messages::internalError(asyncResp->res); 321 return; 322 } 323 asyncResp->res 324 .jsonValue[serverType]["RemoteRoleMapping"][index] 325 ["RemoteGroup"] = *remoteGroup; 326 }, 327 ldapDbusService, roleMapObjData[index].first, 328 propertyInterface, "Set", 329 "xyz.openbmc_project.User.PrivilegeMapperEntry", 330 "GroupName", 331 dbus::utility::DbusVariantType( 332 std::move(*remoteGroup))); 333 } 334 335 // If "LocalRole" info is provided 336 if (localRole) 337 { 338 crow::connections::systemBus->async_method_call( 339 [asyncResp, roleMapObjData, serverType, index, 340 localRole](const boost::system::error_code ec) { 341 if (ec) 342 { 343 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 344 messages::internalError(asyncResp->res); 345 return; 346 } 347 asyncResp->res 348 .jsonValue[serverType]["RemoteRoleMapping"][index] 349 ["LocalRole"] = *localRole; 350 }, 351 ldapDbusService, roleMapObjData[index].first, 352 propertyInterface, "Set", 353 "xyz.openbmc_project.User.PrivilegeMapperEntry", 354 "Privilege", 355 dbus::utility::DbusVariantType( 356 getPrivilegeFromRoleId(std::move(*localRole)))); 357 } 358 } 359 // Create a new RoleMapping Object. 360 else 361 { 362 BMCWEB_LOG_DEBUG 363 << "setRoleMappingProperties: Creating new Object"; 364 std::string pathString = 365 "RemoteRoleMapping/" + std::to_string(index); 366 367 if (!localRole) 368 { 369 messages::propertyMissing(asyncResp->res, 370 pathString + "/LocalRole"); 371 continue; 372 } 373 if (!remoteGroup) 374 { 375 messages::propertyMissing(asyncResp->res, 376 pathString + "/RemoteGroup"); 377 continue; 378 } 379 380 std::string dbusObjectPath; 381 if (serverType == "ActiveDirectory") 382 { 383 dbusObjectPath = adConfigObject; 384 } 385 else if (serverType == "LDAP") 386 { 387 dbusObjectPath = ldapConfigObjectName; 388 } 389 390 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup 391 << ",LocalRole=" << *localRole; 392 393 crow::connections::systemBus->async_method_call( 394 [asyncResp, serverType, localRole, 395 remoteGroup](const boost::system::error_code ec) { 396 if (ec) 397 { 398 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 399 messages::internalError(asyncResp->res); 400 return; 401 } 402 nlohmann::json& remoteRoleJson = 403 asyncResp->res 404 .jsonValue[serverType]["RemoteRoleMapping"]; 405 nlohmann::json::object_t roleMapEntry; 406 roleMapEntry["LocalRole"] = *localRole; 407 roleMapEntry["RemoteGroup"] = *remoteGroup; 408 remoteRoleJson.push_back(std::move(roleMapEntry)); 409 }, 410 ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, 411 "Create", *remoteGroup, 412 getPrivilegeFromRoleId(std::move(*localRole))); 413 } 414 } 415 } 416 } 417 418 /** 419 * Function that retrieves all properties for LDAP config object 420 * into JSON 421 */ 422 template <typename CallbackFunc> 423 inline void getLDAPConfigData(const std::string& ldapType, 424 CallbackFunc&& callback) 425 { 426 427 constexpr std::array<std::string_view, 2> interfaces = { 428 ldapEnableInterface, ldapConfigInterface}; 429 430 dbus::utility::getDbusObject( 431 ldapConfigObjectName, interfaces, 432 [callback, ldapType](const boost::system::error_code& ec, 433 const dbus::utility::MapperGetObject& resp) { 434 if (ec || resp.empty()) 435 { 436 BMCWEB_LOG_ERROR 437 << "DBUS response error during getting of service name: " << ec; 438 LDAPConfigData empty{}; 439 callback(false, empty, ldapType); 440 return; 441 } 442 std::string service = resp.begin()->first; 443 crow::connections::systemBus->async_method_call( 444 [callback, 445 ldapType](const boost::system::error_code errorCode, 446 const dbus::utility::ManagedObjectType& ldapObjects) { 447 LDAPConfigData confData{}; 448 if (errorCode) 449 { 450 callback(false, confData, ldapType); 451 BMCWEB_LOG_ERROR << "D-Bus responses error: " << errorCode; 452 return; 453 } 454 455 std::string ldapDbusType; 456 std::string searchString; 457 458 if (ldapType == "LDAP") 459 { 460 ldapDbusType = 461 "xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap"; 462 searchString = "openldap"; 463 } 464 else if (ldapType == "ActiveDirectory") 465 { 466 ldapDbusType = 467 "xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory"; 468 searchString = "active_directory"; 469 } 470 else 471 { 472 BMCWEB_LOG_ERROR << "Can't get the DbusType for the given type=" 473 << ldapType; 474 callback(false, confData, ldapType); 475 return; 476 } 477 478 std::string ldapEnableInterfaceStr = ldapEnableInterface; 479 std::string ldapConfigInterfaceStr = ldapConfigInterface; 480 481 for (const auto& object : ldapObjects) 482 { 483 // let's find the object whose ldap type is equal to the 484 // given type 485 if (object.first.str.find(searchString) == std::string::npos) 486 { 487 continue; 488 } 489 490 for (const auto& interface : object.second) 491 { 492 if (interface.first == ldapEnableInterfaceStr) 493 { 494 // rest of the properties are string. 495 for (const auto& property : interface.second) 496 { 497 if (property.first == "Enabled") 498 { 499 const bool* value = 500 std::get_if<bool>(&property.second); 501 if (value == nullptr) 502 { 503 continue; 504 } 505 confData.serviceEnabled = *value; 506 break; 507 } 508 } 509 } 510 else if (interface.first == ldapConfigInterfaceStr) 511 { 512 513 for (const auto& property : interface.second) 514 { 515 const std::string* strValue = 516 std::get_if<std::string>(&property.second); 517 if (strValue == nullptr) 518 { 519 continue; 520 } 521 if (property.first == "LDAPServerURI") 522 { 523 confData.uri = *strValue; 524 } 525 else if (property.first == "LDAPBindDN") 526 { 527 confData.bindDN = *strValue; 528 } 529 else if (property.first == "LDAPBaseDN") 530 { 531 confData.baseDN = *strValue; 532 } 533 else if (property.first == "LDAPSearchScope") 534 { 535 confData.searchScope = *strValue; 536 } 537 else if (property.first == "GroupNameAttribute") 538 { 539 confData.groupAttribute = *strValue; 540 } 541 else if (property.first == "UserNameAttribute") 542 { 543 confData.userNameAttribute = *strValue; 544 } 545 else if (property.first == "LDAPType") 546 { 547 confData.serverType = *strValue; 548 } 549 } 550 } 551 else if (interface.first == 552 "xyz.openbmc_project.User.PrivilegeMapperEntry") 553 { 554 LDAPRoleMapData roleMapData{}; 555 for (const auto& property : interface.second) 556 { 557 const std::string* strValue = 558 std::get_if<std::string>(&property.second); 559 560 if (strValue == nullptr) 561 { 562 continue; 563 } 564 565 if (property.first == "GroupName") 566 { 567 roleMapData.groupName = *strValue; 568 } 569 else if (property.first == "Privilege") 570 { 571 roleMapData.privilege = *strValue; 572 } 573 } 574 575 confData.groupRoleList.emplace_back(object.first.str, 576 roleMapData); 577 } 578 } 579 } 580 callback(true, confData, ldapType); 581 }, 582 service, ldapRootObject, dbusObjManagerIntf, "GetManagedObjects"); 583 }); 584 } 585 586 /** 587 * @brief parses the authentication section under the LDAP 588 * @param input JSON data 589 * @param asyncResp pointer to the JSON response 590 * @param userName userName to be filled from the given JSON. 591 * @param password password to be filled from the given JSON. 592 */ 593 inline void parseLDAPAuthenticationJson( 594 nlohmann::json input, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 595 std::optional<std::string>& username, std::optional<std::string>& password) 596 { 597 std::optional<std::string> authType; 598 599 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 600 authType, "Username", username, "Password", 601 password)) 602 { 603 return; 604 } 605 if (!authType) 606 { 607 return; 608 } 609 if (*authType != "UsernameAndPassword") 610 { 611 messages::propertyValueNotInList(asyncResp->res, *authType, 612 "AuthenticationType"); 613 return; 614 } 615 } 616 /** 617 * @brief parses the LDAPService section under the LDAP 618 * @param input JSON data 619 * @param asyncResp pointer to the JSON response 620 * @param baseDNList baseDN to be filled from the given JSON. 621 * @param userNameAttribute userName to be filled from the given JSON. 622 * @param groupaAttribute password to be filled from the given JSON. 623 */ 624 625 inline void 626 parseLDAPServiceJson(nlohmann::json input, 627 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 628 std::optional<std::vector<std::string>>& baseDNList, 629 std::optional<std::string>& userNameAttribute, 630 std::optional<std::string>& groupsAttribute) 631 { 632 std::optional<nlohmann::json> searchSettings; 633 634 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 635 searchSettings)) 636 { 637 return; 638 } 639 if (!searchSettings) 640 { 641 return; 642 } 643 if (!json_util::readJson(*searchSettings, asyncResp->res, 644 "BaseDistinguishedNames", baseDNList, 645 "UsernameAttribute", userNameAttribute, 646 "GroupsAttribute", groupsAttribute)) 647 { 648 return; 649 } 650 } 651 /** 652 * @brief updates the LDAP server address and updates the 653 json response with the new value. 654 * @param serviceAddressList address to be updated. 655 * @param asyncResp pointer to the JSON response 656 * @param ldapServerElementName Type of LDAP 657 server(openLDAP/ActiveDirectory) 658 */ 659 660 inline void handleServiceAddressPatch( 661 const std::vector<std::string>& serviceAddressList, 662 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 663 const std::string& ldapServerElementName, 664 const std::string& ldapConfigObject) 665 { 666 crow::connections::systemBus->async_method_call( 667 [asyncResp, ldapServerElementName, 668 serviceAddressList](const boost::system::error_code ec) { 669 if (ec) 670 { 671 BMCWEB_LOG_DEBUG 672 << "Error Occurred in updating the service address"; 673 messages::internalError(asyncResp->res); 674 return; 675 } 676 std::vector<std::string> modifiedserviceAddressList = { 677 serviceAddressList.front()}; 678 asyncResp->res.jsonValue[ldapServerElementName]["ServiceAddresses"] = 679 modifiedserviceAddressList; 680 if ((serviceAddressList).size() > 1) 681 { 682 messages::propertyValueModified(asyncResp->res, "ServiceAddresses", 683 serviceAddressList.front()); 684 } 685 BMCWEB_LOG_DEBUG << "Updated the service address"; 686 }, 687 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 688 ldapConfigInterface, "LDAPServerURI", 689 dbus::utility::DbusVariantType(serviceAddressList.front())); 690 } 691 /** 692 * @brief updates the LDAP Bind DN and updates the 693 json response with the new value. 694 * @param username name of the user which needs to be updated. 695 * @param asyncResp pointer to the JSON response 696 * @param ldapServerElementName Type of LDAP 697 server(openLDAP/ActiveDirectory) 698 */ 699 700 inline void 701 handleUserNamePatch(const std::string& username, 702 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 703 const std::string& ldapServerElementName, 704 const std::string& ldapConfigObject) 705 { 706 crow::connections::systemBus->async_method_call( 707 [asyncResp, username, 708 ldapServerElementName](const boost::system::error_code ec) { 709 if (ec) 710 { 711 BMCWEB_LOG_DEBUG << "Error occurred in updating the username"; 712 messages::internalError(asyncResp->res); 713 return; 714 } 715 asyncResp->res 716 .jsonValue[ldapServerElementName]["Authentication"]["Username"] = 717 username; 718 BMCWEB_LOG_DEBUG << "Updated the username"; 719 }, 720 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 721 ldapConfigInterface, "LDAPBindDN", 722 dbus::utility::DbusVariantType(username)); 723 } 724 725 /** 726 * @brief updates the LDAP password 727 * @param password : ldap password which needs to be updated. 728 * @param asyncResp pointer to the JSON response 729 * @param ldapServerElementName Type of LDAP 730 * server(openLDAP/ActiveDirectory) 731 */ 732 733 inline void 734 handlePasswordPatch(const std::string& password, 735 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 736 const std::string& ldapServerElementName, 737 const std::string& ldapConfigObject) 738 { 739 crow::connections::systemBus->async_method_call( 740 [asyncResp, password, 741 ldapServerElementName](const boost::system::error_code ec) { 742 if (ec) 743 { 744 BMCWEB_LOG_DEBUG << "Error occurred in updating the password"; 745 messages::internalError(asyncResp->res); 746 return; 747 } 748 asyncResp->res 749 .jsonValue[ldapServerElementName]["Authentication"]["Password"] = 750 ""; 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 1285 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1286 { 1287 return; 1288 } 1289 asyncResp->res.addHeader( 1290 boost::beast::http::field::link, 1291 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1292 } 1293 1294 inline void 1295 handleAccountServiceGet(App& app, const crow::Request& req, 1296 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1297 { 1298 handleAccountServiceHead(app, req, asyncResp); 1299 const persistent_data::AuthConfigMethods& authMethodsConfig = 1300 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1301 1302 nlohmann::json& json = asyncResp->res.jsonValue; 1303 json["@odata.id"] = "/redfish/v1/AccountService"; 1304 json["@odata.type"] = "#AccountService." 1305 "v1_10_0.AccountService"; 1306 json["Id"] = "AccountService"; 1307 json["Name"] = "Account Service"; 1308 json["Description"] = "Account Service"; 1309 json["ServiceEnabled"] = true; 1310 json["MaxPasswordLength"] = 20; 1311 json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; 1312 json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; 1313 json["Oem"]["OpenBMC"]["@odata.type"] = 1314 "#OpenBMCAccountService.v1_0_0.AccountService"; 1315 json["Oem"]["OpenBMC"]["@odata.id"] = 1316 "/redfish/v1/AccountService#/Oem/OpenBMC"; 1317 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = 1318 authMethodsConfig.basic; 1319 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = 1320 authMethodsConfig.sessionToken; 1321 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; 1322 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; 1323 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; 1324 1325 // /redfish/v1/AccountService/LDAP/Certificates is something only 1326 // ConfigureManager can access then only display when the user has 1327 // permissions ConfigureManager 1328 Privileges effectiveUserPrivileges = 1329 redfish::getUserPrivileges(req.userRole); 1330 1331 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 1332 effectiveUserPrivileges)) 1333 { 1334 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = 1335 "/redfish/v1/AccountService/LDAP/Certificates"; 1336 } 1337 sdbusplus::asio::getAllProperties( 1338 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1339 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.AccountPolicy", 1340 [asyncResp](const boost::system::error_code ec, 1341 const dbus::utility::DBusPropertiesMap& propertiesList) { 1342 if (ec) 1343 { 1344 messages::internalError(asyncResp->res); 1345 return; 1346 } 1347 1348 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1349 << "properties for AccountService"; 1350 1351 const uint8_t* minPasswordLength = nullptr; 1352 const uint32_t* accountUnlockTimeout = nullptr; 1353 const uint16_t* maxLoginAttemptBeforeLockout = nullptr; 1354 1355 const bool success = sdbusplus::unpackPropertiesNoThrow( 1356 dbus_utils::UnpackErrorPrinter(), propertiesList, 1357 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout", 1358 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout", 1359 maxLoginAttemptBeforeLockout); 1360 1361 if (!success) 1362 { 1363 messages::internalError(asyncResp->res); 1364 return; 1365 } 1366 1367 if (minPasswordLength != nullptr) 1368 { 1369 asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength; 1370 } 1371 1372 if (accountUnlockTimeout != nullptr) 1373 { 1374 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1375 *accountUnlockTimeout; 1376 } 1377 1378 if (maxLoginAttemptBeforeLockout != nullptr) 1379 { 1380 asyncResp->res.jsonValue["AccountLockoutThreshold"] = 1381 *maxLoginAttemptBeforeLockout; 1382 } 1383 }); 1384 1385 auto callback = [asyncResp](bool success, const LDAPConfigData& confData, 1386 const std::string& ldapType) { 1387 if (!success) 1388 { 1389 return; 1390 } 1391 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1392 }; 1393 1394 getLDAPConfigData("LDAP", callback); 1395 getLDAPConfigData("ActiveDirectory", callback); 1396 } 1397 1398 inline void handleAccountServicePatch( 1399 App& app, const crow::Request& req, 1400 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1401 { 1402 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1403 { 1404 return; 1405 } 1406 std::optional<uint32_t> unlockTimeout; 1407 std::optional<uint16_t> lockoutThreshold; 1408 std::optional<uint8_t> minPasswordLength; 1409 std::optional<uint16_t> maxPasswordLength; 1410 std::optional<nlohmann::json> ldapObject; 1411 std::optional<nlohmann::json> activeDirectoryObject; 1412 std::optional<nlohmann::json> oemObject; 1413 1414 if (!json_util::readJsonPatch( 1415 req, asyncResp->res, "AccountLockoutDuration", unlockTimeout, 1416 "AccountLockoutThreshold", lockoutThreshold, "MaxPasswordLength", 1417 maxPasswordLength, "MinPasswordLength", minPasswordLength, "LDAP", 1418 ldapObject, "ActiveDirectory", activeDirectoryObject, "Oem", 1419 oemObject)) 1420 { 1421 return; 1422 } 1423 1424 if (minPasswordLength) 1425 { 1426 crow::connections::systemBus->async_method_call( 1427 [asyncResp](const boost::system::error_code ec) { 1428 if (ec) 1429 { 1430 messages::internalError(asyncResp->res); 1431 return; 1432 } 1433 messages::success(asyncResp->res); 1434 }, 1435 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1436 "org.freedesktop.DBus.Properties", "Set", 1437 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", 1438 dbus::utility::DbusVariantType(*minPasswordLength)); 1439 } 1440 1441 if (maxPasswordLength) 1442 { 1443 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1444 } 1445 1446 if (ldapObject) 1447 { 1448 handleLDAPPatch(*ldapObject, asyncResp, "LDAP"); 1449 } 1450 1451 if (std::optional<nlohmann::json> oemOpenBMCObject; 1452 oemObject && json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", 1453 oemOpenBMCObject)) 1454 { 1455 if (std::optional<nlohmann::json> authMethodsObject; 1456 oemOpenBMCObject && 1457 json_util::readJson(*oemOpenBMCObject, asyncResp->res, 1458 "AuthMethods", authMethodsObject)) 1459 { 1460 if (authMethodsObject) 1461 { 1462 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1463 } 1464 } 1465 } 1466 1467 if (activeDirectoryObject) 1468 { 1469 handleLDAPPatch(*activeDirectoryObject, asyncResp, "ActiveDirectory"); 1470 } 1471 1472 if (unlockTimeout) 1473 { 1474 crow::connections::systemBus->async_method_call( 1475 [asyncResp](const boost::system::error_code ec) { 1476 if (ec) 1477 { 1478 messages::internalError(asyncResp->res); 1479 return; 1480 } 1481 messages::success(asyncResp->res); 1482 }, 1483 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1484 "org.freedesktop.DBus.Properties", "Set", 1485 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", 1486 dbus::utility::DbusVariantType(*unlockTimeout)); 1487 } 1488 if (lockoutThreshold) 1489 { 1490 crow::connections::systemBus->async_method_call( 1491 [asyncResp](const boost::system::error_code ec) { 1492 if (ec) 1493 { 1494 messages::internalError(asyncResp->res); 1495 return; 1496 } 1497 messages::success(asyncResp->res); 1498 }, 1499 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1500 "org.freedesktop.DBus.Properties", "Set", 1501 "xyz.openbmc_project.User.AccountPolicy", 1502 "MaxLoginAttemptBeforeLockout", 1503 dbus::utility::DbusVariantType(*lockoutThreshold)); 1504 } 1505 } 1506 1507 inline void handleAccountCollectionHead( 1508 App& app, const crow::Request& req, 1509 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1510 { 1511 1512 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1513 { 1514 return; 1515 } 1516 asyncResp->res.addHeader( 1517 boost::beast::http::field::link, 1518 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1519 } 1520 1521 inline void handleAccountCollectionGet( 1522 App& app, const crow::Request& req, 1523 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1524 { 1525 handleAccountCollectionHead(app, req, asyncResp); 1526 1527 asyncResp->res.jsonValue["@odata.id"] = 1528 "/redfish/v1/AccountService/Accounts"; 1529 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." 1530 "ManagerAccountCollection"; 1531 asyncResp->res.jsonValue["Name"] = "Accounts Collection"; 1532 asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; 1533 1534 Privileges effectiveUserPrivileges = 1535 redfish::getUserPrivileges(req.userRole); 1536 1537 std::string thisUser; 1538 if (req.session) 1539 { 1540 thisUser = req.session->username; 1541 } 1542 crow::connections::systemBus->async_method_call( 1543 [asyncResp, thisUser, effectiveUserPrivileges]( 1544 const boost::system::error_code ec, 1545 const dbus::utility::ManagedObjectType& users) { 1546 if (ec) 1547 { 1548 messages::internalError(asyncResp->res); 1549 return; 1550 } 1551 1552 bool userCanSeeAllAccounts = 1553 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); 1554 1555 bool userCanSeeSelf = 1556 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); 1557 1558 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 1559 memberArray = nlohmann::json::array(); 1560 1561 for (const auto& userpath : users) 1562 { 1563 std::string user = userpath.first.filename(); 1564 if (user.empty()) 1565 { 1566 messages::internalError(asyncResp->res); 1567 BMCWEB_LOG_ERROR << "Invalid firmware ID"; 1568 1569 return; 1570 } 1571 1572 // As clarified by Redfish here: 1573 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1574 // Users without ConfigureUsers, only see their own 1575 // account. Users with ConfigureUsers, see all 1576 // accounts. 1577 if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf)) 1578 { 1579 nlohmann::json::object_t member; 1580 member["@odata.id"] = 1581 "/redfish/v1/AccountService/Accounts/" + user; 1582 memberArray.push_back(std::move(member)); 1583 } 1584 } 1585 asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); 1586 }, 1587 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1588 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1589 } 1590 1591 inline void handleAccountCollectionPost( 1592 App& app, const crow::Request& req, 1593 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1594 { 1595 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1596 { 1597 return; 1598 } 1599 std::string username; 1600 std::string password; 1601 std::optional<std::string> roleId("User"); 1602 std::optional<bool> enabled = true; 1603 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, 1604 "Password", password, "RoleId", roleId, 1605 "Enabled", enabled)) 1606 { 1607 return; 1608 } 1609 1610 std::string priv = getPrivilegeFromRoleId(*roleId); 1611 if (priv.empty()) 1612 { 1613 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1614 return; 1615 } 1616 roleId = priv; 1617 1618 // Reading AllGroups property 1619 sdbusplus::asio::getProperty<std::vector<std::string>>( 1620 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1621 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", 1622 "AllGroups", 1623 [asyncResp, username, password{std::move(password)}, roleId, 1624 enabled](const boost::system::error_code ec, 1625 const std::vector<std::string>& allGroupsList) { 1626 if (ec) 1627 { 1628 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1629 messages::internalError(asyncResp->res); 1630 return; 1631 } 1632 1633 if (allGroupsList.empty()) 1634 { 1635 messages::internalError(asyncResp->res); 1636 return; 1637 } 1638 1639 crow::connections::systemBus->async_method_call( 1640 [asyncResp, username, password](const boost::system::error_code ec2, 1641 sdbusplus::message_t& m) { 1642 if (ec2) 1643 { 1644 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); 1645 return; 1646 } 1647 1648 if (pamUpdatePassword(username, password) != PAM_SUCCESS) 1649 { 1650 // At this point we have a user that's been 1651 // created, but the password set 1652 // failed.Something is wrong, so delete the user 1653 // that we've already created 1654 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1655 tempObjPath /= username; 1656 const std::string userPath(tempObjPath); 1657 1658 crow::connections::systemBus->async_method_call( 1659 [asyncResp, password](const boost::system::error_code ec3) { 1660 if (ec3) 1661 { 1662 messages::internalError(asyncResp->res); 1663 return; 1664 } 1665 1666 // If password is invalid 1667 messages::propertyValueFormatError(asyncResp->res, password, 1668 "Password"); 1669 }, 1670 "xyz.openbmc_project.User.Manager", userPath, 1671 "xyz.openbmc_project.Object.Delete", "Delete"); 1672 1673 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1674 return; 1675 } 1676 1677 messages::created(asyncResp->res); 1678 asyncResp->res.addHeader( 1679 "Location", "/redfish/v1/AccountService/Accounts/" + username); 1680 }, 1681 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1682 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1683 allGroupsList, *roleId, *enabled); 1684 }); 1685 } 1686 1687 inline void 1688 handleAccountHead(App& app, const crow::Request& req, 1689 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1690 const std::string& /*accountName*/) 1691 { 1692 1693 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1694 { 1695 return; 1696 } 1697 asyncResp->res.addHeader( 1698 boost::beast::http::field::link, 1699 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1700 } 1701 inline void 1702 handleAccountGet(App& app, const crow::Request& req, 1703 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1704 const std::string& accountName) 1705 { 1706 handleAccountHead(app, req, asyncResp, accountName); 1707 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1708 // If authentication is disabled, there are no user accounts 1709 messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName); 1710 return; 1711 1712 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1713 if (req.session == nullptr) 1714 { 1715 messages::internalError(asyncResp->res); 1716 return; 1717 } 1718 if (req.session->username != accountName) 1719 { 1720 // At this point we've determined that the user is trying to 1721 // modify a user that isn't them. We need to verify that they 1722 // have permissions to modify other users, so re-run the auth 1723 // check with the same permissions, minus ConfigureSelf. 1724 Privileges effectiveUserPrivileges = 1725 redfish::getUserPrivileges(req.userRole); 1726 Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers", 1727 "ConfigureManager"}; 1728 if (!effectiveUserPrivileges.isSupersetOf( 1729 requiredPermissionsToChangeNonSelf)) 1730 { 1731 BMCWEB_LOG_DEBUG << "GET Account denied access"; 1732 messages::insufficientPrivilege(asyncResp->res); 1733 return; 1734 } 1735 } 1736 1737 crow::connections::systemBus->async_method_call( 1738 [asyncResp, 1739 accountName](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 const auto userIt = std::find_if( 1747 users.begin(), users.end(), 1748 [accountName]( 1749 const std::pair<sdbusplus::message::object_path, 1750 dbus::utility::DBusInteracesMap>& user) { 1751 return accountName == user.first.filename(); 1752 }); 1753 1754 if (userIt == users.end()) 1755 { 1756 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1757 accountName); 1758 return; 1759 } 1760 1761 asyncResp->res.jsonValue["@odata.type"] = 1762 "#ManagerAccount.v1_4_0.ManagerAccount"; 1763 asyncResp->res.jsonValue["Name"] = "User Account"; 1764 asyncResp->res.jsonValue["Description"] = "User Account"; 1765 asyncResp->res.jsonValue["Password"] = nullptr; 1766 1767 for (const auto& interface : userIt->second) 1768 { 1769 if (interface.first == "xyz.openbmc_project.User.Attributes") 1770 { 1771 for (const auto& property : interface.second) 1772 { 1773 if (property.first == "UserEnabled") 1774 { 1775 const bool* userEnabled = 1776 std::get_if<bool>(&property.second); 1777 if (userEnabled == nullptr) 1778 { 1779 BMCWEB_LOG_ERROR << "UserEnabled wasn't a bool"; 1780 messages::internalError(asyncResp->res); 1781 return; 1782 } 1783 asyncResp->res.jsonValue["Enabled"] = *userEnabled; 1784 } 1785 else if (property.first == "UserLockedForFailedAttempt") 1786 { 1787 const bool* userLocked = 1788 std::get_if<bool>(&property.second); 1789 if (userLocked == nullptr) 1790 { 1791 BMCWEB_LOG_ERROR << "UserLockedForF" 1792 "ailedAttempt " 1793 "wasn't a bool"; 1794 messages::internalError(asyncResp->res); 1795 return; 1796 } 1797 asyncResp->res.jsonValue["Locked"] = *userLocked; 1798 asyncResp->res 1799 .jsonValue["Locked@Redfish.AllowableValues"] = { 1800 "false"}; // can only unlock accounts 1801 } 1802 else if (property.first == "UserPrivilege") 1803 { 1804 const std::string* userPrivPtr = 1805 std::get_if<std::string>(&property.second); 1806 if (userPrivPtr == nullptr) 1807 { 1808 BMCWEB_LOG_ERROR << "UserPrivilege wasn't a " 1809 "string"; 1810 messages::internalError(asyncResp->res); 1811 return; 1812 } 1813 std::string role = getRoleIdFromPrivilege(*userPrivPtr); 1814 if (role.empty()) 1815 { 1816 BMCWEB_LOG_ERROR << "Invalid user role"; 1817 messages::internalError(asyncResp->res); 1818 return; 1819 } 1820 asyncResp->res.jsonValue["RoleId"] = role; 1821 1822 nlohmann::json& roleEntry = 1823 asyncResp->res.jsonValue["Links"]["Role"]; 1824 roleEntry["@odata.id"] = 1825 "/redfish/v1/AccountService/Roles/" + role; 1826 } 1827 else if (property.first == "UserPasswordExpired") 1828 { 1829 const bool* userPasswordExpired = 1830 std::get_if<bool>(&property.second); 1831 if (userPasswordExpired == nullptr) 1832 { 1833 BMCWEB_LOG_ERROR 1834 << "UserPasswordExpired wasn't a bool"; 1835 messages::internalError(asyncResp->res); 1836 return; 1837 } 1838 asyncResp->res.jsonValue["PasswordChangeRequired"] = 1839 *userPasswordExpired; 1840 } 1841 else if (property.first == "UserGroups") 1842 { 1843 const std::vector<std::string>* userGroups = 1844 std::get_if<std::vector<std::string>>( 1845 &property.second); 1846 if (userGroups == nullptr) 1847 { 1848 BMCWEB_LOG_ERROR 1849 << "userGroups wasn't a string vector"; 1850 messages::internalError(asyncResp->res); 1851 return; 1852 } 1853 if (!translateUserGroup(*userGroups, asyncResp->res)) 1854 { 1855 BMCWEB_LOG_ERROR << "userGroups mapping failed"; 1856 messages::internalError(asyncResp->res); 1857 return; 1858 } 1859 } 1860 } 1861 } 1862 } 1863 1864 asyncResp->res.jsonValue["@odata.id"] = 1865 "/redfish/v1/AccountService/Accounts/" + accountName; 1866 asyncResp->res.jsonValue["Id"] = accountName; 1867 asyncResp->res.jsonValue["UserName"] = accountName; 1868 }, 1869 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1870 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1871 } 1872 1873 inline void 1874 handleAccountDelete(App& app, const crow::Request& req, 1875 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1876 const std::string& username) 1877 { 1878 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1879 { 1880 return; 1881 } 1882 1883 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1884 // If authentication is disabled, there are no user accounts 1885 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 1886 return; 1887 1888 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1889 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1890 tempObjPath /= username; 1891 const std::string userPath(tempObjPath); 1892 1893 crow::connections::systemBus->async_method_call( 1894 [asyncResp, username](const boost::system::error_code ec) { 1895 if (ec) 1896 { 1897 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1898 username); 1899 return; 1900 } 1901 1902 messages::accountRemoved(asyncResp->res); 1903 }, 1904 "xyz.openbmc_project.User.Manager", userPath, 1905 "xyz.openbmc_project.Object.Delete", "Delete"); 1906 } 1907 1908 inline void 1909 handleAccountPatch(App& app, const crow::Request& req, 1910 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1911 const std::string& username) 1912 { 1913 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1914 { 1915 return; 1916 } 1917 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1918 // If authentication is disabled, there are no user accounts 1919 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); 1920 return; 1921 1922 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1923 std::optional<std::string> newUserName; 1924 std::optional<std::string> password; 1925 std::optional<bool> enabled; 1926 std::optional<std::string> roleId; 1927 std::optional<bool> locked; 1928 1929 if (req.session == nullptr) 1930 { 1931 messages::internalError(asyncResp->res); 1932 return; 1933 } 1934 1935 Privileges effectiveUserPrivileges = 1936 redfish::getUserPrivileges(req.userRole); 1937 Privileges configureUsers = {"ConfigureUsers"}; 1938 bool userHasConfigureUsers = 1939 effectiveUserPrivileges.isSupersetOf(configureUsers); 1940 if (userHasConfigureUsers) 1941 { 1942 // Users with ConfigureUsers can modify for all users 1943 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", 1944 newUserName, "Password", password, 1945 "RoleId", roleId, "Enabled", enabled, 1946 "Locked", locked)) 1947 { 1948 return; 1949 } 1950 } 1951 else 1952 { 1953 // ConfigureSelf accounts can only modify their own account 1954 if (username != req.session->username) 1955 { 1956 messages::insufficientPrivilege(asyncResp->res); 1957 return; 1958 } 1959 1960 // ConfigureSelf accounts can only modify their password 1961 if (!json_util::readJsonPatch(req, asyncResp->res, "Password", 1962 password)) 1963 { 1964 return; 1965 } 1966 } 1967 1968 // if user name is not provided in the patch method or if it 1969 // matches the user name in the URI, then we are treating it as 1970 // updating user properties other then username. If username 1971 // provided doesn't match the URI, then we are treating this as 1972 // user rename request. 1973 if (!newUserName || (newUserName.value() == username)) 1974 { 1975 updateUserProperties(asyncResp, username, password, enabled, roleId, 1976 locked); 1977 return; 1978 } 1979 crow::connections::systemBus->async_method_call( 1980 [asyncResp, username, password(std::move(password)), 1981 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, 1982 locked](const boost::system::error_code ec, sdbusplus::message_t& m) { 1983 if (ec) 1984 { 1985 userErrorMessageHandler(m.get_error(), asyncResp, newUser, 1986 username); 1987 return; 1988 } 1989 1990 updateUserProperties(asyncResp, newUser, password, enabled, roleId, 1991 locked); 1992 }, 1993 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1994 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1995 *newUserName); 1996 } 1997 1998 inline void requestAccountServiceRoutes(App& app) 1999 { 2000 2001 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2002 .privileges(redfish::privileges::headAccountService) 2003 .methods(boost::beast::http::verb::head)( 2004 std::bind_front(handleAccountServiceHead, std::ref(app))); 2005 2006 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2007 .privileges(redfish::privileges::getAccountService) 2008 .methods(boost::beast::http::verb::get)( 2009 std::bind_front(handleAccountServiceGet, std::ref(app))); 2010 2011 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 2012 .privileges(redfish::privileges::patchAccountService) 2013 .methods(boost::beast::http::verb::patch)( 2014 std::bind_front(handleAccountServicePatch, std::ref(app))); 2015 2016 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2017 .privileges(redfish::privileges::headManagerAccountCollection) 2018 .methods(boost::beast::http::verb::head)( 2019 std::bind_front(handleAccountCollectionHead, std::ref(app))); 2020 2021 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2022 .privileges(redfish::privileges::getManagerAccountCollection) 2023 .methods(boost::beast::http::verb::get)( 2024 std::bind_front(handleAccountCollectionGet, std::ref(app))); 2025 2026 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 2027 .privileges(redfish::privileges::postManagerAccountCollection) 2028 .methods(boost::beast::http::verb::post)( 2029 std::bind_front(handleAccountCollectionPost, std::ref(app))); 2030 2031 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2032 .privileges(redfish::privileges::headManagerAccount) 2033 .methods(boost::beast::http::verb::head)( 2034 std::bind_front(handleAccountHead, std::ref(app))); 2035 2036 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2037 .privileges(redfish::privileges::getManagerAccount) 2038 .methods(boost::beast::http::verb::get)( 2039 std::bind_front(handleAccountGet, std::ref(app))); 2040 2041 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2042 // TODO this privilege should be using the generated endpoints, but 2043 // because of the special handling of ConfigureSelf, it's not able to 2044 // yet 2045 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) 2046 .methods(boost::beast::http::verb::patch)( 2047 std::bind_front(handleAccountPatch, std::ref(app))); 2048 2049 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2050 .privileges(redfish::privileges::deleteManagerAccount) 2051 .methods(boost::beast::http::verb::delete_)( 2052 std::bind_front(handleAccountDelete, std::ref(app))); 2053 } 2054 2055 } // namespace redfish 2056