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