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