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