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