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