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