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