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