| /* | 
 | // Copyright (c) 2018 Intel Corporation | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //      http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 | */ | 
 | #pragma once | 
 |  | 
 | #ifdef BMCWEB_ENABLE_GRPC | 
 | #include "bmcweb_authorizer_singleton.h" | 
 | #endif | 
 |  | 
 | #include "app.hpp" | 
 | #include "dbus_utility.hpp" | 
 | #include "error_messages.hpp" | 
 | #include "generated/enums/account_service.hpp" | 
 | #include "managed_store.hpp" | 
 | #include "managed_store_types.hpp" | 
 | #include "openbmc_dbus_rest.hpp" | 
 | #include "pam_authenticate.hpp" | 
 | #include "persistent_data.hpp" | 
 | #include "query.hpp" | 
 | #include "registries/privilege_registry.hpp" | 
 | #include "utils/dbus_utils.hpp" | 
 | #include "utils/json_utils.hpp" | 
 |  | 
 | #include <security/pam_appl.h> | 
 |  | 
 | #include <sdbusplus/asio/property.hpp> | 
 | #include <sdbusplus/unpack_properties.hpp> | 
 |  | 
 | #include <array> | 
 | #include <optional> | 
 | #include <string> | 
 | #include <string_view> | 
 | #include <vector> | 
 |  | 
 | #ifdef UNIT_TEST_BUILD | 
 | #include "test/g3/mock_managed_store.hpp" // NOLINT | 
 | #endif | 
 |  | 
 | namespace redfish | 
 | { | 
 |  | 
 | constexpr const char* ldapConfigObjectName = | 
 |     "/xyz/openbmc_project/user/ldap/openldap"; | 
 | constexpr const char* adConfigObject = | 
 |     "/xyz/openbmc_project/user/ldap/active_directory"; | 
 |  | 
 | constexpr const char* rootUserDbusPath = "/xyz/openbmc_project/user/"; | 
 | constexpr const char* ldapRootObject = "/xyz/openbmc_project/user/ldap"; | 
 | constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config"; | 
 | constexpr const char* ldapConfigInterface = | 
 |     "xyz.openbmc_project.User.Ldap.Config"; | 
 | constexpr const char* ldapCreateInterface = | 
 |     "xyz.openbmc_project.User.Ldap.Create"; | 
 | constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable"; | 
 | constexpr const char* ldapPrivMapperInterface = | 
 |     "xyz.openbmc_project.User.PrivilegeMapper"; | 
 | constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; | 
 | constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties"; | 
 |  | 
 | struct LDAPRoleMapData | 
 | { | 
 |     std::string groupName; | 
 |     std::string privilege; | 
 | }; | 
 |  | 
 | struct LDAPConfigData | 
 | { | 
 |     std::string uri; | 
 |     std::string bindDN; | 
 |     std::string baseDN; | 
 |     std::string searchScope; | 
 |     std::string serverType; | 
 |     bool serviceEnabled = false; | 
 |     std::string userNameAttribute; | 
 |     std::string groupAttribute; | 
 |     std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList; | 
 | }; | 
 |  | 
 | inline std::string getRoleIdFromPrivilege(std::string_view role) | 
 | { | 
 |     if (role == "priv-admin") | 
 |     { | 
 |         return "Administrator"; | 
 |     } | 
 |     if (role == "priv-user") | 
 |     { | 
 |         return "ReadOnly"; | 
 |     } | 
 |     if (role == "priv-operator") | 
 |     { | 
 |         return "Operator"; | 
 |     } | 
 |     return ""; | 
 | } | 
 | inline std::string getPrivilegeFromRoleId(std::string_view role) | 
 | { | 
 |     if (role == "Administrator") | 
 |     { | 
 |         return "priv-admin"; | 
 |     } | 
 |     if (role == "ReadOnly") | 
 |     { | 
 |         return "priv-user"; | 
 |     } | 
 |     if (role == "Operator") | 
 |     { | 
 |         return "priv-operator"; | 
 |     } | 
 |     return ""; | 
 | } | 
 |  | 
 | /** | 
 |  * @brief Maps user group names retrieved from D-Bus object to | 
 |  * Account Types. | 
 |  * | 
 |  * @param[in] userGroups List of User groups | 
 |  * @param[out] res AccountTypes populated | 
 |  * | 
 |  * @return true in case of success, false if UserGroups contains | 
 |  * invalid group name(s). | 
 |  */ | 
 | inline bool translateUserGroup(const std::vector<std::string>& userGroups, | 
 |                                crow::Response& res) | 
 | { | 
 |     std::vector<std::string> accountTypes; | 
 |     for (const auto& userGroup : userGroups) | 
 |     { | 
 |         if (userGroup == "redfish") | 
 |         { | 
 |             accountTypes.emplace_back("Redfish"); | 
 |             accountTypes.emplace_back("WebUI"); | 
 |         } | 
 |         else if (userGroup == "ipmi") | 
 |         { | 
 |             accountTypes.emplace_back("IPMI"); | 
 |         } | 
 |         else if (userGroup == "ssh") | 
 |         { | 
 |             accountTypes.emplace_back("HostConsole"); | 
 |             accountTypes.emplace_back("ManagerConsole"); | 
 |         } | 
 |         else if (userGroup == "web") | 
 |         { | 
 |             // 'web' is one of the valid groups in the UserGroups property of | 
 |             // the user account in the D-Bus object. This group is currently not | 
 |             // doing anything, and is considered to be equivalent to 'redfish'. | 
 |             // 'redfish' user group is mapped to 'Redfish'and 'WebUI' | 
 |             // AccountTypes, so do nothing here... | 
 |         } | 
 |         else | 
 |         { | 
 |             // Invalid user group name. Caller throws an excption. | 
 |             return false; | 
 |         } | 
 |     } | 
 |  | 
 |     res.jsonValue["AccountTypes"] = std::move(accountTypes); | 
 |     return true; | 
 | } | 
 |  | 
 | inline void userErrorMessageHandler( | 
 |     const sd_bus_error* e, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |     const std::string& newUser, const std::string& username) | 
 | { | 
 |     if (e == nullptr) | 
 |     { | 
 |         messages::internalError(asyncResp->res); | 
 |         return; | 
 |     } | 
 |  | 
 |     const char* errorMessage = e->name; | 
 |     if (strcmp(errorMessage, | 
 |                "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0) | 
 |     { | 
 |         messages::resourceAlreadyExists(asyncResp->res, "ManagerAccount", | 
 |                                         "UserName", newUser); | 
 |     } | 
 |     else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." | 
 |                                   "UserNameDoesNotExist") == 0) | 
 |     { | 
 |         messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); | 
 |     } | 
 |     else if ((strcmp(errorMessage, | 
 |                      "xyz.openbmc_project.Common.Error.InvalidArgument") == | 
 |               0) || | 
 |              (strcmp( | 
 |                   errorMessage, | 
 |                   "xyz.openbmc_project.User.Common.Error.UserNameGroupFail") == | 
 |               0)) | 
 |     { | 
 |         messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); | 
 |     } | 
 |     else if (strcmp(errorMessage, | 
 |                     "xyz.openbmc_project.User.Common.Error.NoResource") == 0) | 
 |     { | 
 |         messages::createLimitReachedForResource(asyncResp->res); | 
 |     } | 
 |     else | 
 |     { | 
 |         messages::internalError(asyncResp->res); | 
 |     } | 
 | } | 
 |  | 
 | inline void parseLDAPConfigData(nlohmann::json& jsonResponse, | 
 |                                 const LDAPConfigData& confData, | 
 |                                 const std::string& ldapType) | 
 | { | 
 |     std::string service = | 
 |         (ldapType == "LDAP") ? "LDAPService" : "ActiveDirectoryService"; | 
 |  | 
 |     nlohmann::json& ldap = jsonResponse[ldapType]; | 
 |  | 
 |     ldap["ServiceEnabled"] = confData.serviceEnabled; | 
 |     ldap["ServiceAddresses"] = nlohmann::json::array({confData.uri}); | 
 |     ldap["Authentication"]["AuthenticationType"] = | 
 |         account_service::AuthenticationTypes::UsernameAndPassword; | 
 |     ldap["Authentication"]["Username"] = confData.bindDN; | 
 |     ldap["Authentication"]["Password"] = nullptr; | 
 |  | 
 |     ldap["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] = | 
 |         nlohmann::json::array({confData.baseDN}); | 
 |     ldap["LDAPService"]["SearchSettings"]["UsernameAttribute"] = | 
 |         confData.userNameAttribute; | 
 |     ldap["LDAPService"]["SearchSettings"]["GroupsAttribute"] = | 
 |         confData.groupAttribute; | 
 |  | 
 |     nlohmann::json& roleMapArray = ldap["RemoteRoleMapping"]; | 
 |     roleMapArray = nlohmann::json::array(); | 
 |     for (const auto& obj : confData.groupRoleList) | 
 |     { | 
 |         BMCWEB_LOG_DEBUG << "Pushing the data groupName=" | 
 |                          << obj.second.groupName << "\n"; | 
 |  | 
 |         nlohmann::json::object_t remoteGroup; | 
 |         remoteGroup["RemoteGroup"] = obj.second.groupName; | 
 |         remoteGroup["LocalRole"] = getRoleIdFromPrivilege(obj.second.privilege); | 
 |         roleMapArray.emplace_back(std::move(remoteGroup)); | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  *  @brief validates given JSON input and then calls appropriate method to | 
 |  * create, to delete or to set Rolemapping object based on the given input. | 
 |  * | 
 |  */ | 
 | inline void handleRoleMapPatch( | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |     const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData, | 
 |     const std::string& serverType, const std::vector<nlohmann::json>& input) | 
 | { | 
 |     for (size_t index = 0; index < input.size(); index++) | 
 |     { | 
 |         const nlohmann::json& thisJson = input[index]; | 
 |  | 
 |         if (thisJson.is_null()) | 
 |         { | 
 |             // delete the existing object | 
 |             if (index < roleMapObjData.size()) | 
 |             { | 
 |                 managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                     asyncResp->strand_, | 
 |                     [asyncResp, roleMapObjData, serverType, | 
 |                      index](const boost::system::error_code& ec) { | 
 |                     if (ec) | 
 |                     { | 
 |                         BMCWEB_LOG_ERROR << "DBUS response error: " << ec; | 
 |                         messages::internalError(asyncResp->res); | 
 |                         return; | 
 |                     } | 
 |                     asyncResp->res | 
 |                         .jsonValue[serverType]["RemoteRoleMapping"][index] = | 
 |                         nullptr; | 
 |                     }, | 
 |                     ldapDbusService, roleMapObjData[index].first, | 
 |                     "xyz.openbmc_project.Object.Delete", "Delete"); | 
 |             } | 
 |             else | 
 |             { | 
 |                 BMCWEB_LOG_ERROR << "Can't delete the object"; | 
 |                 messages::propertyValueTypeError( | 
 |                     asyncResp->res, | 
 |                     thisJson.dump(2, ' ', true, | 
 |                                   nlohmann::json::error_handler_t::replace), | 
 |                     "RemoteRoleMapping/" + std::to_string(index)); | 
 |                 return; | 
 |             } | 
 |         } | 
 |         else if (thisJson.empty()) | 
 |         { | 
 |             // Don't do anything for the empty objects,parse next json | 
 |             // eg {"RemoteRoleMapping",[{}]} | 
 |         } | 
 |         else | 
 |         { | 
 |             // update/create the object | 
 |             std::optional<std::string> remoteGroup; | 
 |             std::optional<std::string> localRole; | 
 |  | 
 |             // This is a copy, but it's required in this case because of how | 
 |             // readJson is structured | 
 |             nlohmann::json thisJsonCopy = thisJson; | 
 |             if (!json_util::readJson(thisJsonCopy, asyncResp->res, | 
 |                                      "RemoteGroup", remoteGroup, "LocalRole", | 
 |                                      localRole)) | 
 |             { | 
 |                 continue; | 
 |             } | 
 |  | 
 |             // Update existing RoleMapping Object | 
 |             if (index < roleMapObjData.size()) | 
 |             { | 
 |                 BMCWEB_LOG_DEBUG << "Update Role Map Object"; | 
 |                 // If "RemoteGroup" info is provided | 
 |                 if (remoteGroup) | 
 |                 { | 
 |                     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                         asyncResp->strand_, | 
 |                         [asyncResp, roleMapObjData, serverType, index, | 
 |                          remoteGroup](const boost::system::error_code& ec) { | 
 |                         if (ec) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         asyncResp->res | 
 |                             .jsonValue[serverType]["RemoteRoleMapping"][index] | 
 |                                       ["RemoteGroup"] = *remoteGroup; | 
 |                         }, | 
 |                         ldapDbusService, roleMapObjData[index].first, | 
 |                         propertyInterface, "Set", | 
 |                         "xyz.openbmc_project.User.PrivilegeMapperEntry", | 
 |                         "GroupName", | 
 |                         dbus::utility::DbusVariantType( | 
 |                             std::move(*remoteGroup))); | 
 |                 } | 
 |  | 
 |                 // If "LocalRole" info is provided | 
 |                 if (localRole) | 
 |                 { | 
 |                     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                         asyncResp->strand_, | 
 |                         [asyncResp, roleMapObjData, serverType, index, | 
 |                          localRole](const boost::system::error_code& ec) { | 
 |                         if (ec) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         asyncResp->res | 
 |                             .jsonValue[serverType]["RemoteRoleMapping"][index] | 
 |                                       ["LocalRole"] = *localRole; | 
 |                         }, | 
 |                         ldapDbusService, roleMapObjData[index].first, | 
 |                         propertyInterface, "Set", | 
 |                         "xyz.openbmc_project.User.PrivilegeMapperEntry", | 
 |                         "Privilege", | 
 |                         dbus::utility::DbusVariantType( | 
 |                             getPrivilegeFromRoleId(std::move(*localRole)))); | 
 |                 } | 
 |             } | 
 |             // Create a new RoleMapping Object. | 
 |             else | 
 |             { | 
 |                 BMCWEB_LOG_DEBUG | 
 |                     << "setRoleMappingProperties: Creating new Object"; | 
 |                 std::string pathString = | 
 |                     "RemoteRoleMapping/" + std::to_string(index); | 
 |  | 
 |                 if (!localRole) | 
 |                 { | 
 |                     messages::propertyMissing(asyncResp->res, | 
 |                                               pathString + "/LocalRole"); | 
 |                     continue; | 
 |                 } | 
 |                 if (!remoteGroup) | 
 |                 { | 
 |                     messages::propertyMissing(asyncResp->res, | 
 |                                               pathString + "/RemoteGroup"); | 
 |                     continue; | 
 |                 } | 
 |  | 
 |                 std::string dbusObjectPath; | 
 |                 if (serverType == "ActiveDirectory") | 
 |                 { | 
 |                     dbusObjectPath = adConfigObject; | 
 |                 } | 
 |                 else if (serverType == "LDAP") | 
 |                 { | 
 |                     dbusObjectPath = ldapConfigObjectName; | 
 |                 } | 
 |  | 
 |                 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup | 
 |                                  << ",LocalRole=" << *localRole; | 
 |  | 
 |                 managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                     asyncResp->strand_, | 
 |                     [asyncResp, serverType, localRole, | 
 |                      remoteGroup](const boost::system::error_code& ec) { | 
 |                     if (ec) | 
 |                     { | 
 |                         BMCWEB_LOG_ERROR << "DBUS response error: " << ec; | 
 |                         messages::internalError(asyncResp->res); | 
 |                         return; | 
 |                     } | 
 |                     nlohmann::json& remoteRoleJson = | 
 |                         asyncResp->res | 
 |                             .jsonValue[serverType]["RemoteRoleMapping"]; | 
 |                     nlohmann::json::object_t roleMapEntry; | 
 |                     roleMapEntry["LocalRole"] = *localRole; | 
 |                     roleMapEntry["RemoteGroup"] = *remoteGroup; | 
 |                     remoteRoleJson.push_back(std::move(roleMapEntry)); | 
 |                     }, | 
 |                     ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, | 
 |                     "Create", *remoteGroup, | 
 |                     getPrivilegeFromRoleId(std::move(*localRole))); | 
 |             } | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * Function that retrieves all properties for LDAP config object | 
 |  * into JSON | 
 |  */ | 
 | template <typename CallbackFunc> | 
 | inline void | 
 |     getLDAPConfigData(const std::string& ldapType, | 
 |                       const managedStore::ManagedObjectStoreContext& context, | 
 |                       CallbackFunc&& callback) | 
 | { | 
 |  | 
 |     constexpr std::array<std::string_view, 2> interfaces = { | 
 |         ldapEnableInterface, ldapConfigInterface}; | 
 |  | 
 |     managedStore::GetManagedObjectStore()->getDbusObject( | 
 |         ldapConfigObjectName, interfaces, context, | 
 |         [callback, ldapType, | 
 |          context = context](const boost::system::error_code& ec, | 
 |                             const dbus::utility::MapperGetObject& resp) { | 
 |         if (ec || resp.empty()) | 
 |         { | 
 |             BMCWEB_LOG_ERROR | 
 |                 << "DBUS response error during getting of service name: " << ec; | 
 |             LDAPConfigData empty{}; | 
 |             callback(false, empty, ldapType); | 
 |             return; | 
 |         } | 
 |         std::string service = resp.begin()->first; | 
 |  | 
 |         managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
 |             service, {ldapRootObject}, context, | 
 |             [callback, | 
 |              ldapType](const boost::system::error_code& errorCode, | 
 |                        const dbus::utility::ManagedObjectType& ldapObjects) { | 
 |             LDAPConfigData confData{}; | 
 |             if (errorCode) | 
 |             { | 
 |                 callback(false, confData, ldapType); | 
 |                 BMCWEB_LOG_ERROR << "D-Bus responses error: " << errorCode; | 
 |                 return; | 
 |             } | 
 |  | 
 |             std::string ldapDbusType; | 
 |             std::string searchString; | 
 |  | 
 |             if (ldapType == "LDAP") | 
 |             { | 
 |                 ldapDbusType = | 
 |                     "xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap"; | 
 |                 searchString = "openldap"; | 
 |             } | 
 |             else if (ldapType == "ActiveDirectory") | 
 |             { | 
 |                 ldapDbusType = | 
 |                     "xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory"; | 
 |                 searchString = "active_directory"; | 
 |             } | 
 |             else | 
 |             { | 
 |                 BMCWEB_LOG_ERROR << "Can't get the DbusType for the given type=" | 
 |                                  << ldapType; | 
 |                 callback(false, confData, ldapType); | 
 |                 return; | 
 |             } | 
 |  | 
 |             std::string ldapEnableInterfaceStr = ldapEnableInterface; | 
 |             std::string ldapConfigInterfaceStr = ldapConfigInterface; | 
 |  | 
 |             for (const auto& object : ldapObjects) | 
 |             { | 
 |                 // let's find the object whose ldap type is equal to the | 
 |                 // given type | 
 |                 if (object.first.str.find(searchString) == std::string::npos) | 
 |                 { | 
 |                     continue; | 
 |                 } | 
 |  | 
 |                 for (const auto& interface : object.second) | 
 |                 { | 
 |                     if (interface.first == ldapEnableInterfaceStr) | 
 |                     { | 
 |                         // rest of the properties are string. | 
 |                         for (const auto& property : interface.second) | 
 |                         { | 
 |                             if (property.first == "Enabled") | 
 |                             { | 
 |                                 const bool* value = | 
 |                                     std::get_if<bool>(&property.second); | 
 |                                 if (value == nullptr) | 
 |                                 { | 
 |                                     continue; | 
 |                                 } | 
 |                                 confData.serviceEnabled = *value; | 
 |                                 break; | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                     else if (interface.first == ldapConfigInterfaceStr) | 
 |                     { | 
 |  | 
 |                         for (const auto& property : interface.second) | 
 |                         { | 
 |                             const std::string* strValue = | 
 |                                 std::get_if<std::string>(&property.second); | 
 |                             if (strValue == nullptr) | 
 |                             { | 
 |                                 continue; | 
 |                             } | 
 |                             if (property.first == "LDAPServerURI") | 
 |                             { | 
 |                                 confData.uri = *strValue; | 
 |                             } | 
 |                             else if (property.first == "LDAPBindDN") | 
 |                             { | 
 |                                 confData.bindDN = *strValue; | 
 |                             } | 
 |                             else if (property.first == "LDAPBaseDN") | 
 |                             { | 
 |                                 confData.baseDN = *strValue; | 
 |                             } | 
 |                             else if (property.first == "LDAPSearchScope") | 
 |                             { | 
 |                                 confData.searchScope = *strValue; | 
 |                             } | 
 |                             else if (property.first == "GroupNameAttribute") | 
 |                             { | 
 |                                 confData.groupAttribute = *strValue; | 
 |                             } | 
 |                             else if (property.first == "UserNameAttribute") | 
 |                             { | 
 |                                 confData.userNameAttribute = *strValue; | 
 |                             } | 
 |                             else if (property.first == "LDAPType") | 
 |                             { | 
 |                                 confData.serverType = *strValue; | 
 |                             } | 
 |                         } | 
 |                     } | 
 |                     else if (interface.first == | 
 |                              "xyz.openbmc_project.User.PrivilegeMapperEntry") | 
 |                     { | 
 |                         LDAPRoleMapData roleMapData{}; | 
 |                         for (const auto& property : interface.second) | 
 |                         { | 
 |                             const std::string* strValue = | 
 |                                 std::get_if<std::string>(&property.second); | 
 |  | 
 |                             if (strValue == nullptr) | 
 |                             { | 
 |                                 continue; | 
 |                             } | 
 |  | 
 |                             if (property.first == "GroupName") | 
 |                             { | 
 |                                 roleMapData.groupName = *strValue; | 
 |                             } | 
 |                             else if (property.first == "Privilege") | 
 |                             { | 
 |                                 roleMapData.privilege = *strValue; | 
 |                             } | 
 |                         } | 
 |  | 
 |                         confData.groupRoleList.emplace_back(object.first.str, | 
 |                                                             roleMapData); | 
 |                     } | 
 |                 } | 
 |             } | 
 |             callback(true, confData, ldapType); | 
 |             }); | 
 |     }); | 
 | } | 
 |  | 
 | /** | 
 |  * @brief parses the authentication section under the LDAP | 
 |  * @param input JSON data | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param userName  userName to be filled from the given JSON. | 
 |  * @param password  password to be filled from the given JSON. | 
 |  */ | 
 | inline void parseLDAPAuthenticationJson( | 
 |     nlohmann::json input, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |     std::optional<std::string>& username, std::optional<std::string>& password) | 
 | { | 
 |     std::optional<std::string> authType; | 
 |  | 
 |     if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", | 
 |                              authType, "Username", username, "Password", | 
 |                              password)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     if (!authType) | 
 |     { | 
 |         return; | 
 |     } | 
 |     if (*authType != "UsernameAndPassword") | 
 |     { | 
 |         messages::propertyValueNotInList(asyncResp->res, *authType, | 
 |                                          "AuthenticationType"); | 
 |         return; | 
 |     } | 
 | } | 
 | /** | 
 |  * @brief parses the LDAPService section under the LDAP | 
 |  * @param input JSON data | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param baseDNList baseDN to be filled from the given JSON. | 
 |  * @param userNameAttribute  userName to be filled from the given JSON. | 
 |  * @param groupaAttribute  password to be filled from the given JSON. | 
 |  */ | 
 |  | 
 | inline void | 
 |     parseLDAPServiceJson(nlohmann::json input, | 
 |                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                          std::optional<std::vector<std::string>>& baseDNList, | 
 |                          std::optional<std::string>& userNameAttribute, | 
 |                          std::optional<std::string>& groupsAttribute) | 
 | { | 
 |     std::optional<nlohmann::json> searchSettings; | 
 |  | 
 |     if (!json_util::readJson(input, asyncResp->res, "SearchSettings", | 
 |                              searchSettings)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     if (!searchSettings) | 
 |     { | 
 |         return; | 
 |     } | 
 |     if (!json_util::readJson(*searchSettings, asyncResp->res, | 
 |                              "BaseDistinguishedNames", baseDNList, | 
 |                              "UsernameAttribute", userNameAttribute, | 
 |                              "GroupsAttribute", groupsAttribute)) | 
 |     { | 
 |         return; | 
 |     } | 
 | } | 
 | /** | 
 |  * @brief updates the LDAP server address and updates the | 
 |           json response with the new value. | 
 |  * @param serviceAddressList address to be updated. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void handleServiceAddressPatch( | 
 |     const std::vector<std::string>& serviceAddressList, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |     const std::string& ldapServerElementName, | 
 |     const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, ldapServerElementName, | 
 |          serviceAddressList](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG | 
 |                 << "Error Occurred in updating the service address"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         std::vector<std::string> modifiedserviceAddressList = { | 
 |             serviceAddressList.front()}; | 
 |         asyncResp->res.jsonValue[ldapServerElementName]["ServiceAddresses"] = | 
 |             modifiedserviceAddressList; | 
 |         if ((serviceAddressList).size() > 1) | 
 |         { | 
 |             messages::propertyValueModified(asyncResp->res, "ServiceAddresses", | 
 |                                             serviceAddressList.front()); | 
 |         } | 
 |         BMCWEB_LOG_DEBUG << "Updated the service address"; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapConfigInterface, "LDAPServerURI", | 
 |         dbus::utility::DbusVariantType(serviceAddressList.front())); | 
 | } | 
 | /** | 
 |  * @brief updates the LDAP Bind DN and updates the | 
 |           json response with the new value. | 
 |  * @param username name of the user which needs to be updated. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void | 
 |     handleUserNamePatch(const std::string& username, | 
 |                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                         const std::string& ldapServerElementName, | 
 |                         const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, username, | 
 |          ldapServerElementName](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "Error occurred in updating the username"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         asyncResp->res | 
 |             .jsonValue[ldapServerElementName]["Authentication"]["Username"] = | 
 |             username; | 
 |         BMCWEB_LOG_DEBUG << "Updated the username"; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapConfigInterface, "LDAPBindDN", | 
 |         dbus::utility::DbusVariantType(username)); | 
 | } | 
 |  | 
 | /** | 
 |  * @brief updates the LDAP password | 
 |  * @param password : ldap password which needs to be updated. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  *        server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void | 
 |     handlePasswordPatch(const std::string& password, | 
 |                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                         const std::string& ldapServerElementName, | 
 |                         const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, password, | 
 |          ldapServerElementName](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "Error occurred in updating the password"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         asyncResp->res | 
 |             .jsonValue[ldapServerElementName]["Authentication"]["Password"] = | 
 |             ""; | 
 |         BMCWEB_LOG_DEBUG << "Updated the password"; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapConfigInterface, "LDAPBindDNPassword", | 
 |         dbus::utility::DbusVariantType(password)); | 
 | } | 
 |  | 
 | /** | 
 |  * @brief updates the LDAP BaseDN and updates the | 
 |           json response with the new value. | 
 |  * @param baseDNList baseDN list which needs to be updated. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void | 
 |     handleBaseDNPatch(const std::vector<std::string>& baseDNList, | 
 |                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                       const std::string& ldapServerElementName, | 
 |                       const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, baseDNList, | 
 |          ldapServerElementName](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the base DN"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         auto& serverTypeJson = asyncResp->res.jsonValue[ldapServerElementName]; | 
 |         auto& searchSettingsJson = | 
 |             serverTypeJson["LDAPService"]["SearchSettings"]; | 
 |         std::vector<std::string> modifiedBaseDNList = {baseDNList.front()}; | 
 |         searchSettingsJson["BaseDistinguishedNames"] = modifiedBaseDNList; | 
 |         if (baseDNList.size() > 1) | 
 |         { | 
 |             messages::propertyValueModified( | 
 |                 asyncResp->res, "BaseDistinguishedNames", baseDNList.front()); | 
 |         } | 
 |         BMCWEB_LOG_DEBUG << "Updated the base DN"; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapConfigInterface, "LDAPBaseDN", | 
 |         dbus::utility::DbusVariantType(baseDNList.front())); | 
 | } | 
 | /** | 
 |  * @brief updates the LDAP user name attribute and updates the | 
 |           json response with the new value. | 
 |  * @param userNameAttribute attribute to be updated. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void | 
 |     handleUserNameAttrPatch(const std::string& userNameAttribute, | 
 |                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                             const std::string& ldapServerElementName, | 
 |                             const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, userNameAttribute, | 
 |          ldapServerElementName](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the " | 
 |                                 "username attribute"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         auto& serverTypeJson = asyncResp->res.jsonValue[ldapServerElementName]; | 
 |         auto& searchSettingsJson = | 
 |             serverTypeJson["LDAPService"]["SearchSettings"]; | 
 |         searchSettingsJson["UsernameAttribute"] = userNameAttribute; | 
 |         BMCWEB_LOG_DEBUG << "Updated the user name attr."; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapConfigInterface, "UserNameAttribute", | 
 |         dbus::utility::DbusVariantType(userNameAttribute)); | 
 | } | 
 | /** | 
 |  * @brief updates the LDAP group attribute and updates the | 
 |           json response with the new value. | 
 |  * @param groupsAttribute attribute to be updated. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void handleGroupNameAttrPatch( | 
 |     const std::string& groupsAttribute, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |     const std::string& ldapServerElementName, | 
 |     const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, groupsAttribute, | 
 |          ldapServerElementName](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the " | 
 |                                 "groupname attribute"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         auto& serverTypeJson = asyncResp->res.jsonValue[ldapServerElementName]; | 
 |         auto& searchSettingsJson = | 
 |             serverTypeJson["LDAPService"]["SearchSettings"]; | 
 |         searchSettingsJson["GroupsAttribute"] = groupsAttribute; | 
 |         BMCWEB_LOG_DEBUG << "Updated the groupname attr"; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapConfigInterface, "GroupNameAttribute", | 
 |         dbus::utility::DbusVariantType(groupsAttribute)); | 
 | } | 
 | /** | 
 |  * @brief updates the LDAP service enable and updates the | 
 |           json response with the new value. | 
 |  * @param input JSON data. | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param ldapServerElementName Type of LDAP | 
 |  server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void handleServiceEnablePatch( | 
 |     bool serviceEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |     const std::string& ldapServerElementName, | 
 |     const std::string& ldapConfigObject) | 
 | { | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, serviceEnabled, | 
 |          ldapServerElementName](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the service enable"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         asyncResp->res.jsonValue[ldapServerElementName]["ServiceEnabled"] = | 
 |             serviceEnabled; | 
 |         BMCWEB_LOG_DEBUG << "Updated Service enable = " << serviceEnabled; | 
 |         }, | 
 |         ldapDbusService, ldapConfigObject, propertyInterface, "Set", | 
 |         ldapEnableInterface, "Enabled", | 
 |         dbus::utility::DbusVariantType(serviceEnabled)); | 
 | } | 
 |  | 
 | inline void | 
 |     handleAuthMethodsPatch(nlohmann::json& input, | 
 |                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     std::optional<bool> basicAuth; | 
 |     std::optional<bool> cookie; | 
 |     std::optional<bool> sessionToken; | 
 |     std::optional<bool> xToken; | 
 |     std::optional<bool> tls; | 
 |  | 
 |     if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth, | 
 |                              "Cookie", cookie, "SessionToken", sessionToken, | 
 |                              "XToken", xToken, "TLS", tls)) | 
 |     { | 
 |         BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag"; | 
 |         return; | 
 |     } | 
 |  | 
 |     // Make a copy of methods configuration | 
 |     persistent_data::AuthConfigMethods authMethodsConfig = | 
 |         persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); | 
 |  | 
 |     if (basicAuth) | 
 |     { | 
 | #ifndef BMCWEB_ENABLE_BASIC_AUTHENTICATION | 
 |         messages::actionNotSupported( | 
 |             asyncResp->res, | 
 |             "Setting BasicAuth when basic-auth feature is disabled"); | 
 |         return; | 
 | #endif | 
 |         authMethodsConfig.basic = *basicAuth; | 
 |     } | 
 |  | 
 |     if (cookie) | 
 |     { | 
 | #ifndef BMCWEB_ENABLE_COOKIE_AUTHENTICATION | 
 |         messages::actionNotSupported( | 
 |             asyncResp->res, | 
 |             "Setting Cookie when cookie-auth feature is disabled"); | 
 |         return; | 
 | #endif | 
 |         authMethodsConfig.cookie = *cookie; | 
 |     } | 
 |  | 
 |     if (sessionToken) | 
 |     { | 
 | #ifndef BMCWEB_ENABLE_SESSION_AUTHENTICATION | 
 |         messages::actionNotSupported( | 
 |             asyncResp->res, | 
 |             "Setting SessionToken when session-auth feature is disabled"); | 
 |         return; | 
 | #endif | 
 |         authMethodsConfig.sessionToken = *sessionToken; | 
 |     } | 
 |  | 
 |     if (xToken) | 
 |     { | 
 | #ifndef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION | 
 |         messages::actionNotSupported( | 
 |             asyncResp->res, | 
 |             "Setting XToken when xtoken-auth feature is disabled"); | 
 |         return; | 
 | #endif | 
 |         authMethodsConfig.xtoken = *xToken; | 
 |     } | 
 |  | 
 |     if (tls) | 
 |     { | 
 | #ifndef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION | 
 |         messages::actionNotSupported( | 
 |             asyncResp->res, | 
 |             "Setting TLS when mutual-tls-auth feature is disabled"); | 
 |         return; | 
 | #endif | 
 |         authMethodsConfig.tls = *tls; | 
 |     } | 
 |  | 
 |     if (!authMethodsConfig.basic && !authMethodsConfig.cookie && | 
 |         !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken && | 
 |         !authMethodsConfig.tls) | 
 |     { | 
 |         // Do not allow user to disable everything | 
 |         messages::actionNotSupported(asyncResp->res, | 
 |                                      "of disabling all available methods"); | 
 |         return; | 
 |     } | 
 |  | 
 |     persistent_data::SessionStore::getInstance().updateAuthMethodsConfig( | 
 |         authMethodsConfig); | 
 |     // Save configuration immediately | 
 |     persistent_data::getConfig().writeData(); | 
 |  | 
 |     messages::success(asyncResp->res); | 
 | } | 
 |  | 
 | /** | 
 |  * @brief Get the required values from the given JSON, validates the | 
 |  *        value and create the LDAP config object. | 
 |  * @param input JSON data | 
 |  * @param asyncResp pointer to the JSON response | 
 |  * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) | 
 |  */ | 
 |  | 
 | inline void handleLDAPPatch(nlohmann::json& input, | 
 |                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                             const std::string& serverType) | 
 | { | 
 |     managedStore::ManagedObjectStoreContext context(asyncResp); | 
 |  | 
 |     std::string dbusObjectPath; | 
 |     if (serverType == "ActiveDirectory") | 
 |     { | 
 |         dbusObjectPath = adConfigObject; | 
 |     } | 
 |     else if (serverType == "LDAP") | 
 |     { | 
 |         dbusObjectPath = ldapConfigObjectName; | 
 |     } | 
 |     else | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     std::optional<nlohmann::json> authentication; | 
 |     std::optional<nlohmann::json> ldapService; | 
 |     std::optional<std::vector<std::string>> serviceAddressList; | 
 |     std::optional<bool> serviceEnabled; | 
 |     std::optional<std::vector<std::string>> baseDNList; | 
 |     std::optional<std::string> userNameAttribute; | 
 |     std::optional<std::string> groupsAttribute; | 
 |     std::optional<std::string> userName; | 
 |     std::optional<std::string> password; | 
 |     std::optional<std::vector<nlohmann::json>> remoteRoleMapData; | 
 |  | 
 |     if (!json_util::readJson(input, asyncResp->res, "Authentication", | 
 |                              authentication, "LDAPService", ldapService, | 
 |                              "ServiceAddresses", serviceAddressList, | 
 |                              "ServiceEnabled", serviceEnabled, | 
 |                              "RemoteRoleMapping", remoteRoleMapData)) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     if (authentication) | 
 |     { | 
 |         parseLDAPAuthenticationJson(*authentication, asyncResp, userName, | 
 |                                     password); | 
 |     } | 
 |     if (ldapService) | 
 |     { | 
 |         parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, | 
 |                              userNameAttribute, groupsAttribute); | 
 |     } | 
 |     if (serviceAddressList) | 
 |     { | 
 |         if (serviceAddressList->empty()) | 
 |         { | 
 |             messages::propertyValueNotInList(asyncResp->res, "[]", | 
 |                                              "ServiceAddress"); | 
 |             return; | 
 |         } | 
 |     } | 
 |     if (baseDNList) | 
 |     { | 
 |         if (baseDNList->empty()) | 
 |         { | 
 |             messages::propertyValueNotInList(asyncResp->res, "[]", | 
 |                                              "BaseDistinguishedNames"); | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     // nothing to update, then return | 
 |     if (!userName && !password && !serviceAddressList && !baseDNList && | 
 |         !userNameAttribute && !groupsAttribute && !serviceEnabled && | 
 |         !remoteRoleMapData) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     // Get the existing resource first then keep modifying | 
 |     // whenever any property gets updated. | 
 |     getLDAPConfigData( | 
 |         serverType, context, | 
 |         [asyncResp, userName, password, baseDNList, userNameAttribute, | 
 |          groupsAttribute, serviceAddressList, serviceEnabled, dbusObjectPath, | 
 |          remoteRoleMapData](bool success, const LDAPConfigData& confData, | 
 |                             const std::string& serverT) { | 
 |         if (!success) | 
 |         { | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverT); | 
 |         if (confData.serviceEnabled) | 
 |         { | 
 |             // Disable the service first and update the rest of | 
 |             // the properties. | 
 |             handleServiceEnablePatch(false, asyncResp, serverT, dbusObjectPath); | 
 |         } | 
 |  | 
 |         if (serviceAddressList) | 
 |         { | 
 |             handleServiceAddressPatch(*serviceAddressList, asyncResp, serverT, | 
 |                                       dbusObjectPath); | 
 |         } | 
 |         if (userName) | 
 |         { | 
 |             handleUserNamePatch(*userName, asyncResp, serverT, dbusObjectPath); | 
 |         } | 
 |         if (password) | 
 |         { | 
 |             handlePasswordPatch(*password, asyncResp, serverT, dbusObjectPath); | 
 |         } | 
 |  | 
 |         if (baseDNList) | 
 |         { | 
 |             handleBaseDNPatch(*baseDNList, asyncResp, serverT, dbusObjectPath); | 
 |         } | 
 |         if (userNameAttribute) | 
 |         { | 
 |             handleUserNameAttrPatch(*userNameAttribute, asyncResp, serverT, | 
 |                                     dbusObjectPath); | 
 |         } | 
 |         if (groupsAttribute) | 
 |         { | 
 |             handleGroupNameAttrPatch(*groupsAttribute, asyncResp, serverT, | 
 |                                      dbusObjectPath); | 
 |         } | 
 |         if (serviceEnabled) | 
 |         { | 
 |             // if user has given the value as true then enable | 
 |             // the service. if user has given false then no-op | 
 |             // as service is already stopped. | 
 |             if (*serviceEnabled) | 
 |             { | 
 |                 handleServiceEnablePatch(*serviceEnabled, asyncResp, serverT, | 
 |                                          dbusObjectPath); | 
 |             } | 
 |         } | 
 |         else | 
 |         { | 
 |             // if user has not given the service enabled value | 
 |             // then revert it to the same state as it was | 
 |             // before. | 
 |             handleServiceEnablePatch(confData.serviceEnabled, asyncResp, | 
 |                                      serverT, dbusObjectPath); | 
 |         } | 
 |  | 
 |         if (remoteRoleMapData) | 
 |         { | 
 |             handleRoleMapPatch(asyncResp, confData.groupRoleList, serverT, | 
 |                                *remoteRoleMapData); | 
 |         } | 
 |         }); | 
 | } | 
 |  | 
 | inline void updateUserProperties(std::shared_ptr<bmcweb::AsyncResp> asyncResp, | 
 |                                  const std::string& username, | 
 |                                  const std::optional<std::string>& password, | 
 |                                  const std::optional<bool>& enabled, | 
 |                                  const std::optional<std::string>& roleId, | 
 |                                  const std::optional<bool>& locked) | 
 | { | 
 |     sdbusplus::message::object_path tempObjPath(rootUserDbusPath); | 
 |     tempObjPath /= username; | 
 |     std::string dbusObjectPath(tempObjPath); | 
 |  | 
 |     managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
 |     dbus_utils::checkDbusPathExists(dbusObjectPath, requestContext, | 
 |                                     [dbusObjectPath, username, password, roleId, | 
 |                                      enabled, locked, | 
 |                                      asyncResp{std::move(asyncResp)}](int rc) { | 
 |         if (rc <= 0) | 
 |         { | 
 |             messages::resourceNotFound(asyncResp->res, "ManagerAccount", | 
 |                                        username); | 
 |             return; | 
 |         } | 
 |  | 
 |         if (password) | 
 |         { | 
 |             int retval = pamUpdatePassword(username, *password); | 
 |  | 
 |             if (retval == PAM_USER_UNKNOWN) | 
 |             { | 
 |                 messages::resourceNotFound(asyncResp->res, "ManagerAccount", | 
 |                                            username); | 
 |             } | 
 |             else if (retval == PAM_AUTHTOK_ERR) | 
 |             { | 
 |                 // If password is invalid | 
 |                 messages::propertyValueFormatError(asyncResp->res, *password, | 
 |                                                    "Password"); | 
 |                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; | 
 |             } | 
 |             else if (retval != PAM_SUCCESS) | 
 |             { | 
 |                 messages::internalError(asyncResp->res); | 
 |                 return; | 
 |             } | 
 |             else | 
 |             { | 
 |                 messages::success(asyncResp->res); | 
 |             } | 
 |         } | 
 |  | 
 |         if (enabled) | 
 |         { | 
 |             managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                 asyncResp->strand_, | 
 |                 [asyncResp](const boost::system::error_code& ec) { | 
 |                 if (ec) | 
 |                 { | 
 |                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; | 
 |                     messages::internalError(asyncResp->res); | 
 |                     return; | 
 |                 } | 
 |                 messages::success(asyncResp->res); | 
 |                 return; | 
 |             }, | 
 |                 "xyz.openbmc_project.User.Manager", dbusObjectPath, | 
 |                 "org.freedesktop.DBus.Properties", "Set", | 
 |                 "xyz.openbmc_project.User.Attributes", "UserEnabled", | 
 |                 dbus::utility::DbusVariantType{*enabled}); | 
 |         } | 
 |  | 
 |         if (roleId) | 
 |         { | 
 |             std::string priv = getPrivilegeFromRoleId(*roleId); | 
 |             if (priv.empty()) | 
 |             { | 
 |                 messages::propertyValueNotInList(asyncResp->res, *roleId, | 
 |                                                  "RoleId"); | 
 |                 return; | 
 |             } | 
 |  | 
 |             managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                 asyncResp->strand_, | 
 |                 [asyncResp](const boost::system::error_code& ec) { | 
 |                 if (ec) | 
 |                 { | 
 |                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; | 
 |                     messages::internalError(asyncResp->res); | 
 |                     return; | 
 |                 } | 
 |                 messages::success(asyncResp->res); | 
 |             }, | 
 |                 "xyz.openbmc_project.User.Manager", dbusObjectPath, | 
 |                 "org.freedesktop.DBus.Properties", "Set", | 
 |                 "xyz.openbmc_project.User.Attributes", "UserPrivilege", | 
 |                 dbus::utility::DbusVariantType{priv}); | 
 |         } | 
 |  | 
 |         if (locked) | 
 |         { | 
 |             // admin can unlock the account which is locked by | 
 |             // successive authentication failures but admin should | 
 |             // not be allowed to lock an account. | 
 |             if (*locked) | 
 |             { | 
 |                 messages::propertyValueNotInList(asyncResp->res, "true", | 
 |                                                  "Locked"); | 
 |                 return; | 
 |             } | 
 |  | 
 |             managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                 asyncResp->strand_, | 
 |                 [asyncResp](const boost::system::error_code& ec) { | 
 |                 if (ec) | 
 |                 { | 
 |                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; | 
 |                     messages::internalError(asyncResp->res); | 
 |                     return; | 
 |                 } | 
 |                 messages::success(asyncResp->res); | 
 |                 return; | 
 |             }, | 
 |                 "xyz.openbmc_project.User.Manager", dbusObjectPath, | 
 |                 "org.freedesktop.DBus.Properties", "Set", | 
 |                 "xyz.openbmc_project.User.Attributes", | 
 |                 "UserLockedForFailedAttempt", | 
 |                 dbus::utility::DbusVariantType{*locked}); | 
 |         } | 
 |     }); | 
 | } | 
 |  | 
 | inline void handleAccountServiceHead( | 
 |     App& app, const crow::Request& req, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     asyncResp->res.addHeader( | 
 |         boost::beast::http::field::link, | 
 |         "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); | 
 | } | 
 |  | 
 | inline void | 
 |     handleAccountServiceGet(App& app, const crow::Request& req, | 
 |                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     asyncResp->res.addHeader( | 
 |         boost::beast::http::field::link, | 
 |         "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); | 
 |  | 
 |     const persistent_data::AuthConfigMethods& authMethodsConfig = | 
 |         persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); | 
 |  | 
 |     nlohmann::json& json = asyncResp->res.jsonValue; | 
 |     json["@odata.id"] = "/redfish/v1/AccountService"; | 
 |     json["@odata.type"] = "#AccountService." | 
 |                           "v1_10_0.AccountService"; | 
 |     json["Id"] = "AccountService"; | 
 |     json["Name"] = "Account Service"; | 
 |     json["Description"] = "Account Service"; | 
 |     json["ServiceEnabled"] = true; | 
 |     json["MaxPasswordLength"] = 20; | 
 |     json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; | 
 |     json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; | 
 | #ifdef BMCWEB_ENABLE_GRPC | 
 |     json["PrivilegeMap"]["@odata.id"] = | 
 |         "/redfish/v1/AccountService/PrivilegeMap"; | 
 | #endif | 
 |     json["Oem"]["OpenBMC"]["@odata.type"] = | 
 |         "#OpenBMCAccountService.v1_0_0.AccountService"; | 
 |     json["Oem"]["OpenBMC"]["@odata.id"] = | 
 |         "/redfish/v1/AccountService#/Oem/OpenBMC"; | 
 |     json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = | 
 |         authMethodsConfig.basic; | 
 |     json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = | 
 |         authMethodsConfig.sessionToken; | 
 |     json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; | 
 |     json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; | 
 |     json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; | 
 |  | 
 |     // /redfish/v1/AccountService/LDAP/Certificates is something only | 
 |     // ConfigureManager can access then only display when the user has | 
 |     // permissions ConfigureManager | 
 |     Privileges effectiveUserPrivileges = | 
 |         redfish::getUserPrivileges(req.userRole); | 
 |  | 
 |     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, | 
 |                                          effectiveUserPrivileges)) | 
 |     { | 
 |         asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = | 
 |             "/redfish/v1/AccountService/LDAP/Certificates"; | 
 |     } | 
 |     managedStore::ManagedObjectStoreContext context(asyncResp); | 
 |     managedStore::GetManagedObjectStore()->getAllProperties( | 
 |         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |         "xyz.openbmc_project.User.AccountPolicy", context, | 
 |         [asyncResp](const boost::system::error_code& ec, | 
 |                     const dbus::utility::DBusPropertiesMap& propertiesList) { | 
 |         if (ec) | 
 |         { | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |  | 
 |         BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() | 
 |                          << "properties for AccountService"; | 
 |  | 
 |         const uint8_t* minPasswordLength = nullptr; | 
 |         const uint32_t* accountUnlockTimeout = nullptr; | 
 |         const uint16_t* maxLoginAttemptBeforeLockout = nullptr; | 
 |  | 
 |         const bool success = sdbusplus::unpackPropertiesNoThrow( | 
 |             dbus_utils::UnpackErrorPrinter(), propertiesList, | 
 |             "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout", | 
 |             accountUnlockTimeout, "MaxLoginAttemptBeforeLockout", | 
 |             maxLoginAttemptBeforeLockout); | 
 |  | 
 |         if (!success) | 
 |         { | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |  | 
 |         if (minPasswordLength != nullptr) | 
 |         { | 
 |             asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength; | 
 |         } | 
 |  | 
 |         if (accountUnlockTimeout != nullptr) | 
 |         { | 
 |             asyncResp->res.jsonValue["AccountLockoutDuration"] = | 
 |                 *accountUnlockTimeout; | 
 |         } | 
 |  | 
 |         if (maxLoginAttemptBeforeLockout != nullptr) | 
 |         { | 
 |             asyncResp->res.jsonValue["AccountLockoutThreshold"] = | 
 |                 *maxLoginAttemptBeforeLockout; | 
 |         } | 
 |         }); | 
 |  | 
 |     auto callback = [asyncResp](bool success, const LDAPConfigData& confData, | 
 |                                 const std::string& ldapType) { | 
 |         if (!success) | 
 |         { | 
 |             return; | 
 |         } | 
 |         parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); | 
 |     }; | 
 |  | 
 |     getLDAPConfigData("LDAP", context, callback); | 
 |     getLDAPConfigData("ActiveDirectory", context, callback); | 
 | } | 
 |  | 
 | inline void handleAccountServicePatch( | 
 |     App& app, const crow::Request& req, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     std::optional<uint32_t> unlockTimeout; | 
 |     std::optional<uint16_t> lockoutThreshold; | 
 |     std::optional<uint8_t> minPasswordLength; | 
 |     std::optional<uint16_t> maxPasswordLength; | 
 |     std::optional<nlohmann::json> ldapObject; | 
 |     std::optional<nlohmann::json> activeDirectoryObject; | 
 |     std::optional<nlohmann::json> oemObject; | 
 |  | 
 |     if (!json_util::readJsonPatch( | 
 |             req, asyncResp->res, "AccountLockoutDuration", unlockTimeout, | 
 |             "AccountLockoutThreshold", lockoutThreshold, "MaxPasswordLength", | 
 |             maxPasswordLength, "MinPasswordLength", minPasswordLength, "LDAP", | 
 |             ldapObject, "ActiveDirectory", activeDirectoryObject, "Oem", | 
 |             oemObject)) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     if (minPasswordLength) | 
 |     { | 
 |         managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |             asyncResp->strand_, | 
 |             [asyncResp](const boost::system::error_code& ec) { | 
 |             if (ec) | 
 |             { | 
 |                 messages::internalError(asyncResp->res); | 
 |                 return; | 
 |             } | 
 |             messages::success(asyncResp->res); | 
 |             }, | 
 |             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |             "org.freedesktop.DBus.Properties", "Set", | 
 |             "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", | 
 |             dbus::utility::DbusVariantType(*minPasswordLength)); | 
 |     } | 
 |  | 
 |     if (maxPasswordLength) | 
 |     { | 
 |         messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); | 
 |     } | 
 |  | 
 |     if (ldapObject) | 
 |     { | 
 |         handleLDAPPatch(*ldapObject, asyncResp, "LDAP"); | 
 |     } | 
 |  | 
 |     if (std::optional<nlohmann::json> oemOpenBMCObject; | 
 |         oemObject && json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", | 
 |                                          oemOpenBMCObject)) | 
 |     { | 
 |         if (std::optional<nlohmann::json> authMethodsObject; | 
 |             oemOpenBMCObject && | 
 |             json_util::readJson(*oemOpenBMCObject, asyncResp->res, | 
 |                                 "AuthMethods", authMethodsObject)) | 
 |         { | 
 |             if (authMethodsObject) | 
 |             { | 
 |                 handleAuthMethodsPatch(*authMethodsObject, asyncResp); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     if (activeDirectoryObject) | 
 |     { | 
 |         handleLDAPPatch(*activeDirectoryObject, asyncResp, "ActiveDirectory"); | 
 |     } | 
 |  | 
 |     if (unlockTimeout) | 
 |     { | 
 |         managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |             asyncResp->strand_, | 
 |             [asyncResp](const boost::system::error_code& ec) { | 
 |             if (ec) | 
 |             { | 
 |                 messages::internalError(asyncResp->res); | 
 |                 return; | 
 |             } | 
 |             messages::success(asyncResp->res); | 
 |             }, | 
 |             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |             "org.freedesktop.DBus.Properties", "Set", | 
 |             "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", | 
 |             dbus::utility::DbusVariantType(*unlockTimeout)); | 
 |     } | 
 |     if (lockoutThreshold) | 
 |     { | 
 |         managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |             asyncResp->strand_, | 
 |             [asyncResp](const boost::system::error_code& ec) { | 
 |             if (ec) | 
 |             { | 
 |                 messages::internalError(asyncResp->res); | 
 |                 return; | 
 |             } | 
 |             messages::success(asyncResp->res); | 
 |             }, | 
 |             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |             "org.freedesktop.DBus.Properties", "Set", | 
 |             "xyz.openbmc_project.User.AccountPolicy", | 
 |             "MaxLoginAttemptBeforeLockout", | 
 |             dbus::utility::DbusVariantType(*lockoutThreshold)); | 
 |     } | 
 | } | 
 |  | 
 | inline void handleAccountCollectionHead( | 
 |     App& app, const crow::Request& req, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     asyncResp->res.addHeader( | 
 |         boost::beast::http::field::link, | 
 |         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); | 
 | } | 
 |  | 
 | inline void handleAccountCollectionGet( | 
 |     App& app, const crow::Request& req, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     asyncResp->res.addHeader( | 
 |         boost::beast::http::field::link, | 
 |         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); | 
 |  | 
 |     asyncResp->res.jsonValue["@odata.id"] = | 
 |         "/redfish/v1/AccountService/Accounts"; | 
 |     asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." | 
 |                                               "ManagerAccountCollection"; | 
 |     asyncResp->res.jsonValue["Name"] = "Accounts Collection"; | 
 |     asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; | 
 |  | 
 |     Privileges effectiveUserPrivileges = | 
 |         redfish::getUserPrivileges(req.userRole); | 
 |  | 
 |     std::string thisUser; | 
 |     if (req.session) | 
 |     { | 
 |         thisUser = req.session->username; | 
 |     } | 
 |  | 
 |     managedStore::ManagedObjectStoreContext context(asyncResp); | 
 |  | 
 |     managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
 |         "xyz.openbmc_project.User.Manager", {"/xyz/openbmc_project/user"}, | 
 |         context, | 
 |         [asyncResp, thisUser, effectiveUserPrivileges]( | 
 |             const boost::system::error_code& ec, | 
 |             const dbus::utility::ManagedObjectType& users) { | 
 |         if (ec) | 
 |         { | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |  | 
 |         bool userCanSeeAllAccounts = | 
 |             effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); | 
 |  | 
 |         bool userCanSeeSelf = | 
 |             effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); | 
 |  | 
 |         nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; | 
 |         memberArray = nlohmann::json::array(); | 
 |  | 
 |         for (const auto& userpath : users) | 
 |         { | 
 |             std::string user = userpath.first.filename(); | 
 |             if (user.empty()) | 
 |             { | 
 |                 messages::internalError(asyncResp->res); | 
 |                 BMCWEB_LOG_ERROR << "Invalid firmware ID"; | 
 |  | 
 |                 return; | 
 |             } | 
 |  | 
 |             // As clarified by Redfish here: | 
 |             // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration | 
 |             // Users without ConfigureUsers, only see their own | 
 |             // account. Users with ConfigureUsers, see all | 
 |             // accounts. | 
 |             if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf)) | 
 |             { | 
 |                 nlohmann::json::object_t member; | 
 |                 member["@odata.id"] = | 
 |                     "/redfish/v1/AccountService/Accounts/" + user; | 
 |                 memberArray.push_back(std::move(member)); | 
 |             } | 
 |         } | 
 |         asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); | 
 |         }); | 
 | } | 
 |  | 
 | inline void handleAccountCollectionPost( | 
 |     App& app, const crow::Request& req, | 
 |     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     std::string username; | 
 |     std::string password; | 
 |     std::optional<std::string> roleId("User"); | 
 |     std::optional<bool> enabled = true; | 
 |     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, | 
 |                                   "Password", password, "RoleId", roleId, | 
 |                                   "Enabled", enabled)) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 |     std::string priv = getPrivilegeFromRoleId(*roleId); | 
 |     if (priv.empty()) | 
 |     { | 
 |         messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); | 
 |         return; | 
 |     } | 
 |     roleId = priv; | 
 |  | 
 |     // Reading AllGroups property | 
 |     managedStore::ManagedObjectStoreContext requestContext(asyncResp); | 
 |     dbus_utils::getProperty<std::vector<std::string>>( | 
 |         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |         "xyz.openbmc_project.User.Manager", "AllGroups", requestContext, | 
 |         [asyncResp, username, password{std::move(password)}, roleId, | 
 |          enabled](const boost::system::error_code& ec, | 
 |                   const std::vector<std::string>& allGroupsList) { | 
 |         if (ec) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "ERROR with dbus call"; | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |  | 
 |         if (allGroupsList.empty()) | 
 |         { | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |  | 
 |         managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |             asyncResp->strand_, | 
 |             [asyncResp, username, password]( | 
 |                 const boost::system::error_code& ec2, sdbusplus::message_t& m) { | 
 |             if (ec2) | 
 |             { | 
 |                 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); | 
 |                 return; | 
 |             } | 
 |  | 
 |             if (pamUpdatePassword(username, password) != PAM_SUCCESS) | 
 |             { | 
 |                 // At this point we have a user that's been | 
 |                 // created, but the password set | 
 |                 // failed.Something is wrong, so delete the user | 
 |                 // that we've already created | 
 |                 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); | 
 |                 tempObjPath /= username; | 
 |                 const std::string userPath(tempObjPath); | 
 |  | 
 |                 managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |                     asyncResp->strand_, | 
 |                     [asyncResp, | 
 |                      password](const boost::system::error_code& ec3) { | 
 |                     if (ec3) | 
 |                     { | 
 |                         messages::internalError(asyncResp->res); | 
 |                         return; | 
 |                     } | 
 |  | 
 |                     // If password is invalid | 
 |                     messages::propertyValueFormatError(asyncResp->res, password, | 
 |                                                        "Password"); | 
 |                     }, | 
 |                     "xyz.openbmc_project.User.Manager", userPath, | 
 |                     "xyz.openbmc_project.Object.Delete", "Delete"); | 
 |  | 
 |                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; | 
 |                 return; | 
 |             } | 
 |  | 
 |             messages::created(asyncResp->res); | 
 |             asyncResp->res.addHeader( | 
 |                 "Location", "/redfish/v1/AccountService/Accounts/" + username); | 
 |             }, | 
 |             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |             "xyz.openbmc_project.User.Manager", "CreateUser", username, | 
 |             allGroupsList, *roleId, *enabled); | 
 |         }); | 
 | } | 
 |  | 
 | inline void | 
 |     handleAccountHead(App& app, const crow::Request& req, | 
 |                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                       const std::string& /*accountName*/) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     asyncResp->res.addHeader( | 
 |         boost::beast::http::field::link, | 
 |         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); | 
 | } | 
 |  | 
 | inline void | 
 |     handleAccountGet(App& app, const crow::Request& req, | 
 |                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                      const std::string& accountName) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |     asyncResp->res.addHeader( | 
 |         boost::beast::http::field::link, | 
 |         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); | 
 |  | 
 | #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION | 
 |     // If authentication is disabled, there are no user accounts | 
 |     messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName); | 
 |     return; | 
 | #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION | 
 |  | 
 |     if (req.session == nullptr) | 
 |     { | 
 |         messages::internalError(asyncResp->res); | 
 |         return; | 
 |     } | 
 |     if (req.session->username != accountName) | 
 |     { | 
 |         // At this point we've determined that the user is trying to | 
 |         // modify a user that isn't them.  We need to verify that they | 
 |         // have permissions to modify other users, so re-run the auth | 
 |         // check with the same permissions, minus ConfigureSelf. | 
 |         Privileges effectiveUserPrivileges = | 
 |             redfish::getUserPrivileges(req.userRole); | 
 |         Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers", | 
 |                                                          "ConfigureManager"}; | 
 |         if (!effectiveUserPrivileges.isSupersetOf( | 
 |                 requiredPermissionsToChangeNonSelf)) | 
 |         { | 
 |             BMCWEB_LOG_DEBUG << "GET Account denied access"; | 
 |             messages::insufficientPrivilege(asyncResp->res); | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     managedStore::ManagedObjectStoreContext context(asyncResp); | 
 |     managedStore::GetManagedObjectStore()->getManagedObjectsWithContext( | 
 |         "xyz.openbmc_project.User.Manager", {"/xyz/openbmc_project/user"}, | 
 |         context, | 
 |         [asyncResp, | 
 |          accountName](const boost::system::error_code& ec, | 
 |                       const dbus::utility::ManagedObjectType& users) { | 
 |         if (ec) | 
 |         { | 
 |             messages::internalError(asyncResp->res); | 
 |             return; | 
 |         } | 
 |         const auto userIt = std::find_if( | 
 |             users.begin(), users.end(), | 
 |             [accountName]( | 
 |                 const std::pair<sdbusplus::message::object_path, | 
 |                                 dbus::utility::DBusInteracesMap>& user) { | 
 |             return accountName == user.first.filename(); | 
 |             }); | 
 |  | 
 |         if (userIt == users.end()) | 
 |         { | 
 |             messages::resourceNotFound(asyncResp->res, "ManagerAccount", | 
 |                                        accountName); | 
 |             return; | 
 |         } | 
 |  | 
 |         asyncResp->res.jsonValue["@odata.type"] = | 
 |             "#ManagerAccount.v1_4_0.ManagerAccount"; | 
 |         asyncResp->res.jsonValue["Name"] = "User Account"; | 
 |         asyncResp->res.jsonValue["Description"] = "User Account"; | 
 |         asyncResp->res.jsonValue["Password"] = nullptr; | 
 |  | 
 |         for (const auto& interface : userIt->second) | 
 |         { | 
 |             if (interface.first == "xyz.openbmc_project.User.Attributes") | 
 |             { | 
 |                 for (const auto& property : interface.second) | 
 |                 { | 
 |                     if (property.first == "UserEnabled") | 
 |                     { | 
 |                         const bool* userEnabled = | 
 |                             std::get_if<bool>(&property.second); | 
 |                         if (userEnabled == nullptr) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "UserEnabled wasn't a bool"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         asyncResp->res.jsonValue["Enabled"] = *userEnabled; | 
 |                     } | 
 |                     else if (property.first == "UserLockedForFailedAttempt") | 
 |                     { | 
 |                         const bool* userLocked = | 
 |                             std::get_if<bool>(&property.second); | 
 |                         if (userLocked == nullptr) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "UserLockedForF" | 
 |                                                 "ailedAttempt " | 
 |                                                 "wasn't a bool"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         asyncResp->res.jsonValue["Locked"] = *userLocked; | 
 |                         asyncResp->res | 
 |                             .jsonValue["Locked@Redfish.AllowableValues"] = { | 
 |                             "false"}; // can only unlock accounts | 
 |                     } | 
 |                     else if (property.first == "UserPrivilege") | 
 |                     { | 
 |                         const std::string* userPrivPtr = | 
 |                             std::get_if<std::string>(&property.second); | 
 |                         if (userPrivPtr == nullptr) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "UserPrivilege wasn't a " | 
 |                                                 "string"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         std::string role = getRoleIdFromPrivilege(*userPrivPtr); | 
 |                         if (role.empty()) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "Invalid user role"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         asyncResp->res.jsonValue["RoleId"] = role; | 
 |  | 
 |                         nlohmann::json& roleEntry = | 
 |                             asyncResp->res.jsonValue["Links"]["Role"]; | 
 |                         roleEntry["@odata.id"] = | 
 |                             "/redfish/v1/AccountService/Roles/" + role; | 
 |                     } | 
 |                     else if (property.first == "UserPasswordExpired") | 
 |                     { | 
 |                         const bool* userPasswordExpired = | 
 |                             std::get_if<bool>(&property.second); | 
 |                         if (userPasswordExpired == nullptr) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR | 
 |                                 << "UserPasswordExpired wasn't a bool"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         asyncResp->res.jsonValue["PasswordChangeRequired"] = | 
 |                             *userPasswordExpired; | 
 |                     } | 
 |                     else if (property.first == "UserGroups") | 
 |                     { | 
 |                         const std::vector<std::string>* userGroups = | 
 |                             std::get_if<std::vector<std::string>>( | 
 |                                 &property.second); | 
 |                         if (userGroups == nullptr) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR | 
 |                                 << "userGroups wasn't a string vector"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                         if (!translateUserGroup(*userGroups, asyncResp->res)) | 
 |                         { | 
 |                             BMCWEB_LOG_ERROR << "userGroups mapping failed"; | 
 |                             messages::internalError(asyncResp->res); | 
 |                             return; | 
 |                         } | 
 |                     } | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         asyncResp->res.jsonValue["@odata.id"] = | 
 |             "/redfish/v1/AccountService/Accounts/" + accountName; | 
 |         asyncResp->res.jsonValue["Id"] = accountName; | 
 |         asyncResp->res.jsonValue["UserName"] = accountName; | 
 |         }); | 
 | } | 
 |  | 
 | inline void | 
 |     handleAccountDelete(App& app, const crow::Request& req, | 
 |                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                         const std::string& username) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 |  | 
 | #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION | 
 |     // If authentication is disabled, there are no user accounts | 
 |     messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); | 
 |     return; | 
 |  | 
 | #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION | 
 |     sdbusplus::message::object_path tempObjPath(rootUserDbusPath); | 
 |     tempObjPath /= username; | 
 |     const std::string userPath(tempObjPath); | 
 |  | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, username](const boost::system::error_code& ec) { | 
 |         if (ec) | 
 |         { | 
 |             messages::resourceNotFound(asyncResp->res, "ManagerAccount", | 
 |                                        username); | 
 |             return; | 
 |         } | 
 |  | 
 |         messages::accountRemoved(asyncResp->res); | 
 |         }, | 
 |         "xyz.openbmc_project.User.Manager", userPath, | 
 |         "xyz.openbmc_project.Object.Delete", "Delete"); | 
 | } | 
 |  | 
 | inline void | 
 |     handleAccountPatch(App& app, const crow::Request& req, | 
 |                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | 
 |                        const std::string& username) | 
 | { | 
 |     if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |     { | 
 |         return; | 
 |     } | 
 | #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION | 
 |     // If authentication is disabled, there are no user accounts | 
 |     messages::resourceNotFound(asyncResp->res, "ManagerAccount", username); | 
 |     return; | 
 |  | 
 | #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION | 
 |     std::optional<std::string> newUserName; | 
 |     std::optional<std::string> password; | 
 |     std::optional<bool> enabled; | 
 |     std::optional<std::string> roleId; | 
 |     std::optional<bool> locked; | 
 |  | 
 |     if (req.session == nullptr) | 
 |     { | 
 |         messages::internalError(asyncResp->res); | 
 |         return; | 
 |     } | 
 |  | 
 |     Privileges effectiveUserPrivileges = | 
 |         redfish::getUserPrivileges(req.userRole); | 
 |     Privileges configureUsers = {"ConfigureUsers"}; | 
 |     bool userHasConfigureUsers = | 
 |         effectiveUserPrivileges.isSupersetOf(configureUsers); | 
 |     if (userHasConfigureUsers) | 
 |     { | 
 |         // Users with ConfigureUsers can modify for all users | 
 |         if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", | 
 |                                       newUserName, "Password", password, | 
 |                                       "RoleId", roleId, "Enabled", enabled, | 
 |                                       "Locked", locked)) | 
 |         { | 
 |             return; | 
 |         } | 
 |     } | 
 |     else | 
 |     { | 
 |         // ConfigureSelf accounts can only modify their own account | 
 |         if (username != req.session->username) | 
 |         { | 
 |             messages::insufficientPrivilege(asyncResp->res); | 
 |             return; | 
 |         } | 
 |  | 
 |         // ConfigureSelf accounts can only modify their password | 
 |         if (!json_util::readJsonPatch(req, asyncResp->res, "Password", | 
 |                                       password)) | 
 |         { | 
 |             return; | 
 |         } | 
 |     } | 
 |  | 
 |     // if user name is not provided in the patch method or if it | 
 |     // matches the user name in the URI, then we are treating it as | 
 |     // updating user properties other then username. If username | 
 |     // provided doesn't match the URI, then we are treating this as | 
 |     // user rename request. | 
 |     if (!newUserName || (newUserName.value() == username)) | 
 |     { | 
 |         updateUserProperties(asyncResp, username, password, enabled, roleId, | 
 |                              locked); | 
 |         return; | 
 |     } | 
 |     managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | 
 |         asyncResp->strand_, | 
 |         [asyncResp, username, password(std::move(password)), | 
 |          roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, | 
 |          locked](const boost::system::error_code& ec, sdbusplus::message_t& m) { | 
 |         if (ec) | 
 |         { | 
 |             userErrorMessageHandler(m.get_error(), asyncResp, newUser, | 
 |                                     username); | 
 |             return; | 
 |         } | 
 |  | 
 |         updateUserProperties(asyncResp, newUser, password, enabled, roleId, | 
 |                              locked); | 
 |         }, | 
 |         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", | 
 |         "xyz.openbmc_project.User.Manager", "RenameUser", username, | 
 |         *newUserName); | 
 | } | 
 |  | 
 | inline void requestAccountServiceRoutes(App& app) | 
 | { | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") | 
 |         .privileges(redfish::privileges::headAccountService) | 
 |         .methods(boost::beast::http::verb::head)( | 
 |             std::bind_front(handleAccountServiceHead, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") | 
 |         .privileges(redfish::privileges::getAccountService) | 
 |         .methods(boost::beast::http::verb::get)( | 
 |             std::bind_front(handleAccountServiceGet, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") | 
 |         .privileges(redfish::privileges::patchAccountService) | 
 |         .methods(boost::beast::http::verb::patch)( | 
 |             std::bind_front(handleAccountServicePatch, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") | 
 |         .privileges(redfish::privileges::headManagerAccountCollection) | 
 |         .methods(boost::beast::http::verb::head)( | 
 |             std::bind_front(handleAccountCollectionHead, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") | 
 |         .privileges(redfish::privileges::getManagerAccountCollection) | 
 |         .methods(boost::beast::http::verb::get)( | 
 |             std::bind_front(handleAccountCollectionGet, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") | 
 |         .privileges(redfish::privileges::postManagerAccountCollection) | 
 |         .methods(boost::beast::http::verb::post)( | 
 |             std::bind_front(handleAccountCollectionPost, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") | 
 |         .privileges(redfish::privileges::headManagerAccount) | 
 |         .methods(boost::beast::http::verb::head)( | 
 |             std::bind_front(handleAccountHead, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") | 
 |         .privileges(redfish::privileges::getManagerAccount) | 
 |         .methods(boost::beast::http::verb::get)( | 
 |             std::bind_front(handleAccountGet, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") | 
 |         // TODO this privilege should be using the generated endpoints, but | 
 |         // because of the special handling of ConfigureSelf, it's not able to | 
 |         // yet | 
 |         .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) | 
 |         .methods(boost::beast::http::verb::patch)( | 
 |             std::bind_front(handleAccountPatch, std::ref(app))); | 
 |  | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") | 
 |         .privileges(redfish::privileges::deleteManagerAccount) | 
 |         .methods(boost::beast::http::verb::delete_)( | 
 |             std::bind_front(handleAccountDelete, std::ref(app))); | 
 |  | 
 | #ifdef BMCWEB_ENABLE_GRPC | 
 |     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/PrivilegeMap/") | 
 |         // Privileges obtained from https://github.com/DMTF/Redfish/pull/5474 | 
 |         .privileges({{"Login"}}) | 
 |         .methods(boost::beast::http::verb::get)( | 
 |             [&app](const crow::Request& req, | 
 |                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | 
 |         if (!redfish::setUpRedfishRoute(app, req, asyncResp)) | 
 |         { | 
 |             return; | 
 |         } | 
 |  | 
 |         asyncResp->res.jsonValue = | 
 |             ::milotic::authz::BmcWebAuthorizerSingleton::GetInstance() | 
 |                 .GetRedfishPrivilegeRegistry(); | 
 |         }); | 
 | #endif | 
 | } | 
 |  | 
 | } // namespace redfish |