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