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