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