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