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