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