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