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("ManagerConsole");
139         }
140         else if (userGroup == "hostconsole")
141         {
142             // The hostconsole group controls who can access the host console
143             // port via ssh and websocket.
144             accountTypes.emplace_back("HostConsole");
145         }
146         else if (userGroup == "web")
147         {
148             // 'web' is one of the valid groups in the UserGroups property of
149             // the user account in the D-Bus object. This group is currently not
150             // doing anything, and is considered to be equivalent to 'redfish'.
151             // 'redfish' user group is mapped to 'Redfish'and 'WebUI'
152             // AccountTypes, so do nothing here...
153         }
154         else
155         {
156             // Invalid user group name. Caller throws an excption.
157             return false;
158         }
159     }
160 
161     res.jsonValue["AccountTypes"] = std::move(accountTypes);
162     return true;
163 }
164 
165 inline void userErrorMessageHandler(
166     const sd_bus_error* e, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
167     const std::string& newUser, const std::string& username)
168 {
169     if (e == nullptr)
170     {
171         messages::internalError(asyncResp->res);
172         return;
173     }
174 
175     const char* errorMessage = e->name;
176     if (strcmp(errorMessage,
177                "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0)
178     {
179         messages::resourceAlreadyExists(asyncResp->res, "ManagerAccount",
180                                         "UserName", newUser);
181     }
182     else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error."
183                                   "UserNameDoesNotExist") == 0)
184     {
185         messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
186     }
187     else if ((strcmp(errorMessage,
188                      "xyz.openbmc_project.Common.Error.InvalidArgument") ==
189               0) ||
190              (strcmp(
191                   errorMessage,
192                   "xyz.openbmc_project.User.Common.Error.UserNameGroupFail") ==
193               0))
194     {
195         messages::propertyValueFormatError(asyncResp->res, newUser, "UserName");
196     }
197     else if (strcmp(errorMessage,
198                     "xyz.openbmc_project.User.Common.Error.NoResource") == 0)
199     {
200         messages::createLimitReachedForResource(asyncResp->res);
201     }
202     else
203     {
204         messages::internalError(asyncResp->res);
205     }
206 }
207 
208 inline void parseLDAPConfigData(nlohmann::json& jsonResponse,
209                                 const LDAPConfigData& confData,
210                                 const std::string& ldapType)
211 {
212     std::string service = (ldapType == "LDAP") ? "LDAPService"
213                                                : "ActiveDirectoryService";
214 
215     nlohmann::json& ldap = jsonResponse[ldapType];
216 
217     ldap["ServiceEnabled"] = confData.serviceEnabled;
218     ldap["ServiceAddresses"] = nlohmann::json::array({confData.uri});
219     ldap["Authentication"]["AuthenticationType"] =
220         account_service::AuthenticationTypes::UsernameAndPassword;
221     ldap["Authentication"]["Username"] = confData.bindDN;
222     ldap["Authentication"]["Password"] = nullptr;
223 
224     ldap["LDAPService"]["SearchSettings"]["BaseDistinguishedNames"] =
225         nlohmann::json::array({confData.baseDN});
226     ldap["LDAPService"]["SearchSettings"]["UsernameAttribute"] =
227         confData.userNameAttribute;
228     ldap["LDAPService"]["SearchSettings"]["GroupsAttribute"] =
229         confData.groupAttribute;
230 
231     nlohmann::json& roleMapArray = ldap["RemoteRoleMapping"];
232     roleMapArray = nlohmann::json::array();
233     for (const auto& obj : confData.groupRoleList)
234     {
235         BMCWEB_LOG_DEBUG << "Pushing the data groupName="
236                          << obj.second.groupName << "\n";
237 
238         nlohmann::json::object_t remoteGroup;
239         remoteGroup["RemoteGroup"] = obj.second.groupName;
240         remoteGroup["LocalRole"] = getRoleIdFromPrivilege(obj.second.privilege);
241         roleMapArray.emplace_back(std::move(remoteGroup));
242     }
243 }
244 
245 /**
246  *  @brief validates given JSON input and then calls appropriate method to
247  * create, to delete or to set Rolemapping object based on the given input.
248  *
249  */
250 inline void handleRoleMapPatch(
251     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
252     const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData,
253     const std::string& serverType, const std::vector<nlohmann::json>& input)
254 {
255     for (size_t index = 0; index < input.size(); index++)
256     {
257         const nlohmann::json& thisJson = input[index];
258 
259         if (thisJson.is_null())
260         {
261             // delete the existing object
262             if (index < roleMapObjData.size())
263             {
264                 crow::connections::systemBus->async_method_call(
265                     [asyncResp, roleMapObjData, serverType,
266                      index](const boost::system::error_code& ec) {
267                     if (ec)
268                     {
269                         BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
270                         messages::internalError(asyncResp->res);
271                         return;
272                     }
273                     asyncResp->res.jsonValue[serverType]["RemoteRoleMapping"]
274                                             [index] = nullptr;
275                     },
276                     ldapDbusService, roleMapObjData[index].first,
277                     "xyz.openbmc_project.Object.Delete", "Delete");
278             }
279             else
280             {
281                 BMCWEB_LOG_ERROR << "Can't delete the object";
282                 messages::propertyValueTypeError(
283                     asyncResp->res,
284                     thisJson.dump(2, ' ', true,
285                                   nlohmann::json::error_handler_t::replace),
286                     "RemoteRoleMapping/" + std::to_string(index));
287                 return;
288             }
289         }
290         else if (thisJson.empty())
291         {
292             // Don't do anything for the empty objects,parse next json
293             // eg {"RemoteRoleMapping",[{}]}
294         }
295         else
296         {
297             // update/create the object
298             std::optional<std::string> remoteGroup;
299             std::optional<std::string> localRole;
300 
301             // This is a copy, but it's required in this case because of how
302             // readJson is structured
303             nlohmann::json thisJsonCopy = thisJson;
304             if (!json_util::readJson(thisJsonCopy, asyncResp->res,
305                                      "RemoteGroup", remoteGroup, "LocalRole",
306                                      localRole))
307             {
308                 continue;
309             }
310 
311             // Update existing RoleMapping Object
312             if (index < roleMapObjData.size())
313             {
314                 BMCWEB_LOG_DEBUG << "Update Role Map Object";
315                 // If "RemoteGroup" info is provided
316                 if (remoteGroup)
317                 {
318                     crow::connections::systemBus->async_method_call(
319                         [asyncResp, roleMapObjData, serverType, index,
320                          remoteGroup](const boost::system::error_code& ec) {
321                         if (ec)
322                         {
323                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
324                             messages::internalError(asyncResp->res);
325                             return;
326                         }
327                         asyncResp->res
328                             .jsonValue[serverType]["RemoteRoleMapping"][index]
329                                       ["RemoteGroup"] = *remoteGroup;
330                         },
331                         ldapDbusService, roleMapObjData[index].first,
332                         propertyInterface, "Set",
333                         "xyz.openbmc_project.User.PrivilegeMapperEntry",
334                         "GroupName",
335                         dbus::utility::DbusVariantType(
336                             std::move(*remoteGroup)));
337                 }
338 
339                 // If "LocalRole" info is provided
340                 if (localRole)
341                 {
342                     crow::connections::systemBus->async_method_call(
343                         [asyncResp, roleMapObjData, serverType, index,
344                          localRole](const boost::system::error_code& ec) {
345                         if (ec)
346                         {
347                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
348                             messages::internalError(asyncResp->res);
349                             return;
350                         }
351                         asyncResp->res
352                             .jsonValue[serverType]["RemoteRoleMapping"][index]
353                                       ["LocalRole"] = *localRole;
354                         },
355                         ldapDbusService, roleMapObjData[index].first,
356                         propertyInterface, "Set",
357                         "xyz.openbmc_project.User.PrivilegeMapperEntry",
358                         "Privilege",
359                         dbus::utility::DbusVariantType(
360                             getPrivilegeFromRoleId(std::move(*localRole))));
361                 }
362             }
363             // Create a new RoleMapping Object.
364             else
365             {
366                 BMCWEB_LOG_DEBUG
367                     << "setRoleMappingProperties: Creating new Object";
368                 std::string pathString = "RemoteRoleMapping/" +
369                                          std::to_string(index);
370 
371                 if (!localRole)
372                 {
373                     messages::propertyMissing(asyncResp->res,
374                                               pathString + "/LocalRole");
375                     continue;
376                 }
377                 if (!remoteGroup)
378                 {
379                     messages::propertyMissing(asyncResp->res,
380                                               pathString + "/RemoteGroup");
381                     continue;
382                 }
383 
384                 std::string dbusObjectPath;
385                 if (serverType == "ActiveDirectory")
386                 {
387                     dbusObjectPath = adConfigObject;
388                 }
389                 else if (serverType == "LDAP")
390                 {
391                     dbusObjectPath = ldapConfigObjectName;
392                 }
393 
394                 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup
395                                  << ",LocalRole=" << *localRole;
396 
397                 crow::connections::systemBus->async_method_call(
398                     [asyncResp, serverType, localRole,
399                      remoteGroup](const boost::system::error_code& ec) {
400                     if (ec)
401                     {
402                         BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
403                         messages::internalError(asyncResp->res);
404                         return;
405                     }
406                     nlohmann::json& remoteRoleJson =
407                         asyncResp->res
408                             .jsonValue[serverType]["RemoteRoleMapping"];
409                     nlohmann::json::object_t roleMapEntry;
410                     roleMapEntry["LocalRole"] = *localRole;
411                     roleMapEntry["RemoteGroup"] = *remoteGroup;
412                     remoteRoleJson.emplace_back(std::move(roleMapEntry));
413                     },
414                     ldapDbusService, dbusObjectPath, ldapPrivMapperInterface,
415                     "Create", *remoteGroup,
416                     getPrivilegeFromRoleId(std::move(*localRole)));
417             }
418         }
419     }
420 }
421 
422 /**
423  * Function that retrieves all properties for LDAP config object
424  * into JSON
425  */
426 template <typename CallbackFunc>
427 inline void getLDAPConfigData(const std::string& ldapType,
428                               CallbackFunc&& callback)
429 {
430     constexpr std::array<std::string_view, 2> interfaces = {
431         ldapEnableInterface, ldapConfigInterface};
432 
433     dbus::utility::getDbusObject(
434         ldapConfigObjectName, interfaces,
435         [callback, ldapType](const boost::system::error_code& ec,
436                              const dbus::utility::MapperGetObject& resp) {
437         if (ec || resp.empty())
438         {
439             BMCWEB_LOG_ERROR
440                 << "DBUS response error during getting of service name: " << ec;
441             LDAPConfigData empty{};
442             callback(false, empty, ldapType);
443             return;
444         }
445         std::string service = resp.begin()->first;
446         crow::connections::systemBus->async_method_call(
447             [callback,
448              ldapType](const boost::system::error_code& errorCode,
449                        const dbus::utility::ManagedObjectType& ldapObjects) {
450             LDAPConfigData confData{};
451             if (errorCode)
452             {
453                 callback(false, confData, ldapType);
454                 BMCWEB_LOG_ERROR << "D-Bus responses error: " << errorCode;
455                 return;
456             }
457 
458             std::string ldapDbusType;
459             std::string searchString;
460 
461             if (ldapType == "LDAP")
462             {
463                 ldapDbusType =
464                     "xyz.openbmc_project.User.Ldap.Config.Type.OpenLdap";
465                 searchString = "openldap";
466             }
467             else if (ldapType == "ActiveDirectory")
468             {
469                 ldapDbusType =
470                     "xyz.openbmc_project.User.Ldap.Config.Type.ActiveDirectory";
471                 searchString = "active_directory";
472             }
473             else
474             {
475                 BMCWEB_LOG_ERROR << "Can't get the DbusType for the given type="
476                                  << ldapType;
477                 callback(false, confData, ldapType);
478                 return;
479             }
480 
481             std::string ldapEnableInterfaceStr = ldapEnableInterface;
482             std::string ldapConfigInterfaceStr = ldapConfigInterface;
483 
484             for (const auto& object : ldapObjects)
485             {
486                 // let's find the object whose ldap type is equal to the
487                 // given type
488                 if (object.first.str.find(searchString) == std::string::npos)
489                 {
490                     continue;
491                 }
492 
493                 for (const auto& interface : object.second)
494                 {
495                     if (interface.first == ldapEnableInterfaceStr)
496                     {
497                         // rest of the properties are string.
498                         for (const auto& property : interface.second)
499                         {
500                             if (property.first == "Enabled")
501                             {
502                                 const bool* value =
503                                     std::get_if<bool>(&property.second);
504                                 if (value == nullptr)
505                                 {
506                                     continue;
507                                 }
508                                 confData.serviceEnabled = *value;
509                                 break;
510                             }
511                         }
512                     }
513                     else if (interface.first == ldapConfigInterfaceStr)
514                     {
515                         for (const auto& property : interface.second)
516                         {
517                             const std::string* strValue =
518                                 std::get_if<std::string>(&property.second);
519                             if (strValue == nullptr)
520                             {
521                                 continue;
522                             }
523                             if (property.first == "LDAPServerURI")
524                             {
525                                 confData.uri = *strValue;
526                             }
527                             else if (property.first == "LDAPBindDN")
528                             {
529                                 confData.bindDN = *strValue;
530                             }
531                             else if (property.first == "LDAPBaseDN")
532                             {
533                                 confData.baseDN = *strValue;
534                             }
535                             else if (property.first == "LDAPSearchScope")
536                             {
537                                 confData.searchScope = *strValue;
538                             }
539                             else if (property.first == "GroupNameAttribute")
540                             {
541                                 confData.groupAttribute = *strValue;
542                             }
543                             else if (property.first == "UserNameAttribute")
544                             {
545                                 confData.userNameAttribute = *strValue;
546                             }
547                             else if (property.first == "LDAPType")
548                             {
549                                 confData.serverType = *strValue;
550                             }
551                         }
552                     }
553                     else if (interface.first ==
554                              "xyz.openbmc_project.User.PrivilegeMapperEntry")
555                     {
556                         LDAPRoleMapData roleMapData{};
557                         for (const auto& property : interface.second)
558                         {
559                             const std::string* strValue =
560                                 std::get_if<std::string>(&property.second);
561 
562                             if (strValue == nullptr)
563                             {
564                                 continue;
565                             }
566 
567                             if (property.first == "GroupName")
568                             {
569                                 roleMapData.groupName = *strValue;
570                             }
571                             else if (property.first == "Privilege")
572                             {
573                                 roleMapData.privilege = *strValue;
574                             }
575                         }
576 
577                         confData.groupRoleList.emplace_back(object.first.str,
578                                                             roleMapData);
579                     }
580                 }
581             }
582             callback(true, confData, ldapType);
583             },
584             service, ldapRootObject, dbusObjManagerIntf, "GetManagedObjects");
585         });
586 }
587 
588 /**
589  * @brief parses the authentication section under the LDAP
590  * @param input JSON data
591  * @param asyncResp pointer to the JSON response
592  * @param userName  userName to be filled from the given JSON.
593  * @param password  password to be filled from the given JSON.
594  */
595 inline void parseLDAPAuthenticationJson(
596     nlohmann::json input, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
597     std::optional<std::string>& username, std::optional<std::string>& password)
598 {
599     std::optional<std::string> authType;
600 
601     if (!json_util::readJson(input, asyncResp->res, "AuthenticationType",
602                              authType, "Username", username, "Password",
603                              password))
604     {
605         return;
606     }
607     if (!authType)
608     {
609         return;
610     }
611     if (*authType != "UsernameAndPassword")
612     {
613         messages::propertyValueNotInList(asyncResp->res, *authType,
614                                          "AuthenticationType");
615         return;
616     }
617 }
618 /**
619  * @brief parses the LDAPService section under the LDAP
620  * @param input JSON data
621  * @param asyncResp pointer to the JSON response
622  * @param baseDNList baseDN to be filled from the given JSON.
623  * @param userNameAttribute  userName to be filled from the given JSON.
624  * @param groupaAttribute  password to be filled from the given JSON.
625  */
626 
627 inline void
628     parseLDAPServiceJson(nlohmann::json input,
629                          const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
630                          std::optional<std::vector<std::string>>& baseDNList,
631                          std::optional<std::string>& userNameAttribute,
632                          std::optional<std::string>& groupsAttribute)
633 {
634     std::optional<nlohmann::json> searchSettings;
635 
636     if (!json_util::readJson(input, asyncResp->res, "SearchSettings",
637                              searchSettings))
638     {
639         return;
640     }
641     if (!searchSettings)
642     {
643         return;
644     }
645     if (!json_util::readJson(*searchSettings, asyncResp->res,
646                              "BaseDistinguishedNames", baseDNList,
647                              "UsernameAttribute", userNameAttribute,
648                              "GroupsAttribute", groupsAttribute))
649     {
650         return;
651     }
652 }
653 /**
654  * @brief updates the LDAP server address and updates the
655           json response with the new value.
656  * @param serviceAddressList address to be updated.
657  * @param asyncResp pointer to the JSON response
658  * @param ldapServerElementName Type of LDAP
659  server(openLDAP/ActiveDirectory)
660  */
661 
662 inline void handleServiceAddressPatch(
663     const std::vector<std::string>& serviceAddressList,
664     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
665     const std::string& ldapServerElementName,
666     const std::string& ldapConfigObject)
667 {
668     crow::connections::systemBus->async_method_call(
669         [asyncResp, ldapServerElementName,
670          serviceAddressList](const boost::system::error_code& ec) {
671         if (ec)
672         {
673             BMCWEB_LOG_DEBUG
674                 << "Error Occurred in updating the service address";
675             messages::internalError(asyncResp->res);
676             return;
677         }
678         std::vector<std::string> modifiedserviceAddressList = {
679             serviceAddressList.front()};
680         asyncResp->res.jsonValue[ldapServerElementName]["ServiceAddresses"] =
681             modifiedserviceAddressList;
682         if ((serviceAddressList).size() > 1)
683         {
684             messages::propertyValueModified(asyncResp->res, "ServiceAddresses",
685                                             serviceAddressList.front());
686         }
687         BMCWEB_LOG_DEBUG << "Updated the service address";
688         },
689         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
690         ldapConfigInterface, "LDAPServerURI",
691         dbus::utility::DbusVariantType(serviceAddressList.front()));
692 }
693 /**
694  * @brief updates the LDAP Bind DN and updates the
695           json response with the new value.
696  * @param username name of the user which needs to be updated.
697  * @param asyncResp pointer to the JSON response
698  * @param ldapServerElementName Type of LDAP
699  server(openLDAP/ActiveDirectory)
700  */
701 
702 inline void
703     handleUserNamePatch(const std::string& username,
704                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
705                         const std::string& ldapServerElementName,
706                         const std::string& ldapConfigObject)
707 {
708     crow::connections::systemBus->async_method_call(
709         [asyncResp, username,
710          ldapServerElementName](const boost::system::error_code& ec) {
711         if (ec)
712         {
713             BMCWEB_LOG_DEBUG << "Error occurred in updating the username";
714             messages::internalError(asyncResp->res);
715             return;
716         }
717         asyncResp->res.jsonValue[ldapServerElementName]["Authentication"]
718                                 ["Username"] = username;
719         BMCWEB_LOG_DEBUG << "Updated the username";
720         },
721         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
722         ldapConfigInterface, "LDAPBindDN",
723         dbus::utility::DbusVariantType(username));
724 }
725 
726 /**
727  * @brief updates the LDAP password
728  * @param password : ldap password which needs to be updated.
729  * @param asyncResp pointer to the JSON response
730  * @param ldapServerElementName Type of LDAP
731  *        server(openLDAP/ActiveDirectory)
732  */
733 
734 inline void
735     handlePasswordPatch(const std::string& password,
736                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
737                         const std::string& ldapServerElementName,
738                         const std::string& ldapConfigObject)
739 {
740     crow::connections::systemBus->async_method_call(
741         [asyncResp, password,
742          ldapServerElementName](const boost::system::error_code& ec) {
743         if (ec)
744         {
745             BMCWEB_LOG_DEBUG << "Error occurred in updating the password";
746             messages::internalError(asyncResp->res);
747             return;
748         }
749         asyncResp->res.jsonValue[ldapServerElementName]["Authentication"]
750                                 ["Password"] = "";
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 
1302     if (req.session == nullptr)
1303     {
1304         messages::internalError(asyncResp->res);
1305         return;
1306     }
1307 
1308     asyncResp->res.addHeader(
1309         boost::beast::http::field::link,
1310         "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby");
1311 
1312     const persistent_data::AuthConfigMethods& authMethodsConfig =
1313         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1314 
1315     nlohmann::json& json = asyncResp->res.jsonValue;
1316     json["@odata.id"] = "/redfish/v1/AccountService";
1317     json["@odata.type"] = "#AccountService."
1318                           "v1_10_0.AccountService";
1319     json["Id"] = "AccountService";
1320     json["Name"] = "Account Service";
1321     json["Description"] = "Account Service";
1322     json["ServiceEnabled"] = true;
1323     json["MaxPasswordLength"] = 20;
1324     json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts";
1325     json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
1326     json["Oem"]["OpenBMC"]["@odata.type"] =
1327         "#OpenBMCAccountService.v1_0_0.AccountService";
1328     json["Oem"]["OpenBMC"]["@odata.id"] =
1329         "/redfish/v1/AccountService#/Oem/OpenBMC";
1330     json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] =
1331         authMethodsConfig.basic;
1332     json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] =
1333         authMethodsConfig.sessionToken;
1334     json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken;
1335     json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie;
1336     json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls;
1337 
1338     // /redfish/v1/AccountService/LDAP/Certificates is something only
1339     // ConfigureManager can access then only display when the user has
1340     // permissions ConfigureManager
1341     Privileges effectiveUserPrivileges =
1342         redfish::getUserPrivileges(*req.session);
1343 
1344     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
1345                                          effectiveUserPrivileges))
1346     {
1347         asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] =
1348             "/redfish/v1/AccountService/LDAP/Certificates";
1349     }
1350     sdbusplus::asio::getAllProperties(
1351         *crow::connections::systemBus, "xyz.openbmc_project.User.Manager",
1352         "/xyz/openbmc_project/user", "xyz.openbmc_project.User.AccountPolicy",
1353         [asyncResp](const boost::system::error_code& ec,
1354                     const dbus::utility::DBusPropertiesMap& propertiesList) {
1355         if (ec)
1356         {
1357             messages::internalError(asyncResp->res);
1358             return;
1359         }
1360 
1361         BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
1362                          << "properties for AccountService";
1363 
1364         const uint8_t* minPasswordLength = nullptr;
1365         const uint32_t* accountUnlockTimeout = nullptr;
1366         const uint16_t* maxLoginAttemptBeforeLockout = nullptr;
1367 
1368         const bool success = sdbusplus::unpackPropertiesNoThrow(
1369             dbus_utils::UnpackErrorPrinter(), propertiesList,
1370             "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout",
1371             accountUnlockTimeout, "MaxLoginAttemptBeforeLockout",
1372             maxLoginAttemptBeforeLockout);
1373 
1374         if (!success)
1375         {
1376             messages::internalError(asyncResp->res);
1377             return;
1378         }
1379 
1380         if (minPasswordLength != nullptr)
1381         {
1382             asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength;
1383         }
1384 
1385         if (accountUnlockTimeout != nullptr)
1386         {
1387             asyncResp->res.jsonValue["AccountLockoutDuration"] =
1388                 *accountUnlockTimeout;
1389         }
1390 
1391         if (maxLoginAttemptBeforeLockout != nullptr)
1392         {
1393             asyncResp->res.jsonValue["AccountLockoutThreshold"] =
1394                 *maxLoginAttemptBeforeLockout;
1395         }
1396         });
1397 
1398     auto callback = [asyncResp](bool success, const LDAPConfigData& confData,
1399                                 const std::string& ldapType) {
1400         if (!success)
1401         {
1402             return;
1403         }
1404         parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1405     };
1406 
1407     getLDAPConfigData("LDAP", callback);
1408     getLDAPConfigData("ActiveDirectory", callback);
1409 }
1410 
1411 inline void handleAccountServicePatch(
1412     App& app, const crow::Request& req,
1413     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1414 {
1415     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1416     {
1417         return;
1418     }
1419     std::optional<uint32_t> unlockTimeout;
1420     std::optional<uint16_t> lockoutThreshold;
1421     std::optional<uint8_t> minPasswordLength;
1422     std::optional<uint16_t> maxPasswordLength;
1423     std::optional<nlohmann::json> ldapObject;
1424     std::optional<nlohmann::json> activeDirectoryObject;
1425     std::optional<nlohmann::json> oemObject;
1426 
1427     if (!json_util::readJsonPatch(
1428             req, asyncResp->res, "AccountLockoutDuration", unlockTimeout,
1429             "AccountLockoutThreshold", lockoutThreshold, "MaxPasswordLength",
1430             maxPasswordLength, "MinPasswordLength", minPasswordLength, "LDAP",
1431             ldapObject, "ActiveDirectory", activeDirectoryObject, "Oem",
1432             oemObject))
1433     {
1434         return;
1435     }
1436 
1437     if (minPasswordLength)
1438     {
1439         crow::connections::systemBus->async_method_call(
1440             [asyncResp](const boost::system::error_code& ec) {
1441             if (ec)
1442             {
1443                 messages::internalError(asyncResp->res);
1444                 return;
1445             }
1446             messages::success(asyncResp->res);
1447             },
1448             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1449             "org.freedesktop.DBus.Properties", "Set",
1450             "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength",
1451             dbus::utility::DbusVariantType(*minPasswordLength));
1452     }
1453 
1454     if (maxPasswordLength)
1455     {
1456         messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1457     }
1458 
1459     if (ldapObject)
1460     {
1461         handleLDAPPatch(*ldapObject, asyncResp, "LDAP");
1462     }
1463 
1464     if (std::optional<nlohmann::json> oemOpenBMCObject;
1465         oemObject && json_util::readJson(*oemObject, asyncResp->res, "OpenBMC",
1466                                          oemOpenBMCObject))
1467     {
1468         if (std::optional<nlohmann::json> authMethodsObject;
1469             oemOpenBMCObject &&
1470             json_util::readJson(*oemOpenBMCObject, asyncResp->res,
1471                                 "AuthMethods", authMethodsObject))
1472         {
1473             if (authMethodsObject)
1474             {
1475                 handleAuthMethodsPatch(*authMethodsObject, asyncResp);
1476             }
1477         }
1478     }
1479 
1480     if (activeDirectoryObject)
1481     {
1482         handleLDAPPatch(*activeDirectoryObject, asyncResp, "ActiveDirectory");
1483     }
1484 
1485     if (unlockTimeout)
1486     {
1487         crow::connections::systemBus->async_method_call(
1488             [asyncResp](const boost::system::error_code& ec) {
1489             if (ec)
1490             {
1491                 messages::internalError(asyncResp->res);
1492                 return;
1493             }
1494             messages::success(asyncResp->res);
1495             },
1496             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1497             "org.freedesktop.DBus.Properties", "Set",
1498             "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout",
1499             dbus::utility::DbusVariantType(*unlockTimeout));
1500     }
1501     if (lockoutThreshold)
1502     {
1503         crow::connections::systemBus->async_method_call(
1504             [asyncResp](const boost::system::error_code& ec) {
1505             if (ec)
1506             {
1507                 messages::internalError(asyncResp->res);
1508                 return;
1509             }
1510             messages::success(asyncResp->res);
1511             },
1512             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1513             "org.freedesktop.DBus.Properties", "Set",
1514             "xyz.openbmc_project.User.AccountPolicy",
1515             "MaxLoginAttemptBeforeLockout",
1516             dbus::utility::DbusVariantType(*lockoutThreshold));
1517     }
1518 }
1519 
1520 inline void handleAccountCollectionHead(
1521     App& app, const crow::Request& req,
1522     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1523 {
1524     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1525     {
1526         return;
1527     }
1528     asyncResp->res.addHeader(
1529         boost::beast::http::field::link,
1530         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1531 }
1532 
1533 inline void handleAccountCollectionGet(
1534     App& app, const crow::Request& req,
1535     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1536 {
1537     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1538     {
1539         return;
1540     }
1541 
1542     if (req.session == nullptr)
1543     {
1544         messages::internalError(asyncResp->res);
1545         return;
1546     }
1547 
1548     asyncResp->res.addHeader(
1549         boost::beast::http::field::link,
1550         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1551 
1552     asyncResp->res.jsonValue["@odata.id"] =
1553         "/redfish/v1/AccountService/Accounts";
1554     asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection."
1555                                               "ManagerAccountCollection";
1556     asyncResp->res.jsonValue["Name"] = "Accounts Collection";
1557     asyncResp->res.jsonValue["Description"] = "BMC User Accounts";
1558 
1559     Privileges effectiveUserPrivileges =
1560         redfish::getUserPrivileges(*req.session);
1561 
1562     std::string thisUser;
1563     if (req.session)
1564     {
1565         thisUser = req.session->username;
1566     }
1567     crow::connections::systemBus->async_method_call(
1568         [asyncResp, thisUser, effectiveUserPrivileges](
1569             const boost::system::error_code& ec,
1570             const dbus::utility::ManagedObjectType& users) {
1571         if (ec)
1572         {
1573             messages::internalError(asyncResp->res);
1574             return;
1575         }
1576 
1577         bool userCanSeeAllAccounts =
1578             effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"});
1579 
1580         bool userCanSeeSelf =
1581             effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"});
1582 
1583         nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
1584         memberArray = nlohmann::json::array();
1585 
1586         for (const auto& userpath : users)
1587         {
1588             std::string user = userpath.first.filename();
1589             if (user.empty())
1590             {
1591                 messages::internalError(asyncResp->res);
1592                 BMCWEB_LOG_ERROR << "Invalid firmware ID";
1593 
1594                 return;
1595             }
1596 
1597             // As clarified by Redfish here:
1598             // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1599             // Users without ConfigureUsers, only see their own
1600             // account. Users with ConfigureUsers, see all
1601             // accounts.
1602             if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf))
1603             {
1604                 nlohmann::json::object_t member;
1605                 member["@odata.id"] = "/redfish/v1/AccountService/Accounts/" +
1606                                       user;
1607                 memberArray.emplace_back(std::move(member));
1608             }
1609         }
1610         asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size();
1611         },
1612         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1613         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1614 }
1615 
1616 inline void processAfterCreateUser(
1617     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1618     const std::string& username, const std::string& password,
1619     const boost::system::error_code& ec, sdbusplus::message_t& m)
1620 {
1621     if (ec)
1622     {
1623         userErrorMessageHandler(m.get_error(), asyncResp, username, "");
1624         return;
1625     }
1626 
1627     if (pamUpdatePassword(username, password) != PAM_SUCCESS)
1628     {
1629         // At this point we have a user that's been
1630         // created, but the password set
1631         // failed.Something is wrong, so delete the user
1632         // that we've already created
1633         sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1634         tempObjPath /= username;
1635         const std::string userPath(tempObjPath);
1636 
1637         crow::connections::systemBus->async_method_call(
1638             [asyncResp, password](const boost::system::error_code& ec3) {
1639             if (ec3)
1640             {
1641                 messages::internalError(asyncResp->res);
1642                 return;
1643             }
1644 
1645             // If password is invalid
1646             messages::propertyValueFormatError(asyncResp->res, password,
1647                                                "Password");
1648             },
1649             "xyz.openbmc_project.User.Manager", userPath,
1650             "xyz.openbmc_project.Object.Delete", "Delete");
1651 
1652         BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1653         return;
1654     }
1655 
1656     messages::created(asyncResp->res);
1657     asyncResp->res.addHeader("Location",
1658                              "/redfish/v1/AccountService/Accounts/" + username);
1659 }
1660 
1661 inline void processAfterGetAllGroups(
1662     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1663     const std::string& username, const std::string& password,
1664     const std::optional<std::string>& roleId, std::optional<bool> enabled,
1665     const std::vector<std::string>& allGroupsList)
1666 
1667 {
1668     std::vector<std::string> userGroups;
1669     for (const auto& grp : allGroupsList)
1670     {
1671         // Console access is provided to the user who is a member of
1672         // hostconsole group and has a administrator role. So, set
1673         // hostconsole group only for the administrator.
1674         if ((grp != "hostconsole") || (roleId == "priv-admin"))
1675         {
1676             userGroups.emplace_back(grp);
1677         }
1678     }
1679 
1680     crow::connections::systemBus->async_method_call(
1681         [asyncResp, username, password](const boost::system::error_code& ec2,
1682                                         sdbusplus::message_t& m) {
1683         processAfterCreateUser(asyncResp, username, password, ec2, m);
1684         },
1685         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1686         "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups,
1687         *roleId, *enabled);
1688 }
1689 
1690 inline void handleAccountCollectionPost(
1691     App& app, const crow::Request& req,
1692     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1693 {
1694     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1695     {
1696         return;
1697     }
1698     std::string username;
1699     std::string password;
1700     std::optional<std::string> roleId("User");
1701     std::optional<bool> enabled = true;
1702     if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username,
1703                                   "Password", password, "RoleId", roleId,
1704                                   "Enabled", enabled))
1705     {
1706         return;
1707     }
1708 
1709     std::string priv = getPrivilegeFromRoleId(*roleId);
1710     if (priv.empty())
1711     {
1712         messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
1713         return;
1714     }
1715     roleId = priv;
1716 
1717     // Reading AllGroups property
1718     sdbusplus::asio::getProperty<std::vector<std::string>>(
1719         *crow::connections::systemBus, "xyz.openbmc_project.User.Manager",
1720         "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager",
1721         "AllGroups",
1722         [asyncResp, username, password{std::move(password)}, roleId,
1723          enabled](const boost::system::error_code& ec,
1724                   const std::vector<std::string>& allGroupsList) {
1725         if (ec)
1726         {
1727             BMCWEB_LOG_DEBUG << "ERROR with async_method_call";
1728             messages::internalError(asyncResp->res);
1729             return;
1730         }
1731 
1732         if (allGroupsList.empty())
1733         {
1734             messages::internalError(asyncResp->res);
1735             return;
1736         }
1737 
1738         processAfterGetAllGroups(asyncResp, username, password, roleId, enabled,
1739                                  allGroupsList);
1740         });
1741 }
1742 
1743 inline void
1744     handleAccountHead(App& app, const crow::Request& req,
1745                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1746                       const std::string& /*accountName*/)
1747 {
1748     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1749     {
1750         return;
1751     }
1752     asyncResp->res.addHeader(
1753         boost::beast::http::field::link,
1754         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1755 }
1756 
1757 inline void
1758     handleAccountGet(App& app, const crow::Request& req,
1759                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1760                      const std::string& accountName)
1761 {
1762     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1763     {
1764         return;
1765     }
1766     asyncResp->res.addHeader(
1767         boost::beast::http::field::link,
1768         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1769 
1770 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1771     // If authentication is disabled, there are no user accounts
1772     messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName);
1773     return;
1774 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1775 
1776     if (req.session == nullptr)
1777     {
1778         messages::internalError(asyncResp->res);
1779         return;
1780     }
1781     if (req.session->username != accountName)
1782     {
1783         // At this point we've determined that the user is trying to
1784         // modify a user that isn't them.  We need to verify that they
1785         // have permissions to modify other users, so re-run the auth
1786         // check with the same permissions, minus ConfigureSelf.
1787         Privileges effectiveUserPrivileges =
1788             redfish::getUserPrivileges(*req.session);
1789         Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers",
1790                                                          "ConfigureManager"};
1791         if (!effectiveUserPrivileges.isSupersetOf(
1792                 requiredPermissionsToChangeNonSelf))
1793         {
1794             BMCWEB_LOG_DEBUG << "GET Account denied access";
1795             messages::insufficientPrivilege(asyncResp->res);
1796             return;
1797         }
1798     }
1799 
1800     crow::connections::systemBus->async_method_call(
1801         [asyncResp,
1802          accountName](const boost::system::error_code& ec,
1803                       const dbus::utility::ManagedObjectType& users) {
1804         if (ec)
1805         {
1806             messages::internalError(asyncResp->res);
1807             return;
1808         }
1809         const auto userIt = std::find_if(
1810             users.begin(), users.end(),
1811             [accountName](
1812                 const std::pair<sdbusplus::message::object_path,
1813                                 dbus::utility::DBusInteracesMap>& user) {
1814             return accountName == user.first.filename();
1815             });
1816 
1817         if (userIt == users.end())
1818         {
1819             messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1820                                        accountName);
1821             return;
1822         }
1823 
1824         asyncResp->res.jsonValue["@odata.type"] =
1825             "#ManagerAccount.v1_4_0.ManagerAccount";
1826         asyncResp->res.jsonValue["Name"] = "User Account";
1827         asyncResp->res.jsonValue["Description"] = "User Account";
1828         asyncResp->res.jsonValue["Password"] = nullptr;
1829 
1830         for (const auto& interface : userIt->second)
1831         {
1832             if (interface.first == "xyz.openbmc_project.User.Attributes")
1833             {
1834                 for (const auto& property : interface.second)
1835                 {
1836                     if (property.first == "UserEnabled")
1837                     {
1838                         const bool* userEnabled =
1839                             std::get_if<bool>(&property.second);
1840                         if (userEnabled == nullptr)
1841                         {
1842                             BMCWEB_LOG_ERROR << "UserEnabled wasn't a bool";
1843                             messages::internalError(asyncResp->res);
1844                             return;
1845                         }
1846                         asyncResp->res.jsonValue["Enabled"] = *userEnabled;
1847                     }
1848                     else if (property.first == "UserLockedForFailedAttempt")
1849                     {
1850                         const bool* userLocked =
1851                             std::get_if<bool>(&property.second);
1852                         if (userLocked == nullptr)
1853                         {
1854                             BMCWEB_LOG_ERROR << "UserLockedForF"
1855                                                 "ailedAttempt "
1856                                                 "wasn't a bool";
1857                             messages::internalError(asyncResp->res);
1858                             return;
1859                         }
1860                         asyncResp->res.jsonValue["Locked"] = *userLocked;
1861                         asyncResp->res
1862                             .jsonValue["Locked@Redfish.AllowableValues"] = {
1863                             "false"}; // can only unlock accounts
1864                     }
1865                     else if (property.first == "UserPrivilege")
1866                     {
1867                         const std::string* userPrivPtr =
1868                             std::get_if<std::string>(&property.second);
1869                         if (userPrivPtr == nullptr)
1870                         {
1871                             BMCWEB_LOG_ERROR << "UserPrivilege wasn't a "
1872                                                 "string";
1873                             messages::internalError(asyncResp->res);
1874                             return;
1875                         }
1876                         std::string role = getRoleIdFromPrivilege(*userPrivPtr);
1877                         if (role.empty())
1878                         {
1879                             BMCWEB_LOG_ERROR << "Invalid user role";
1880                             messages::internalError(asyncResp->res);
1881                             return;
1882                         }
1883                         asyncResp->res.jsonValue["RoleId"] = role;
1884 
1885                         nlohmann::json& roleEntry =
1886                             asyncResp->res.jsonValue["Links"]["Role"];
1887                         roleEntry["@odata.id"] =
1888                             "/redfish/v1/AccountService/Roles/" + role;
1889                     }
1890                     else if (property.first == "UserPasswordExpired")
1891                     {
1892                         const bool* userPasswordExpired =
1893                             std::get_if<bool>(&property.second);
1894                         if (userPasswordExpired == nullptr)
1895                         {
1896                             BMCWEB_LOG_ERROR
1897                                 << "UserPasswordExpired wasn't a bool";
1898                             messages::internalError(asyncResp->res);
1899                             return;
1900                         }
1901                         asyncResp->res.jsonValue["PasswordChangeRequired"] =
1902                             *userPasswordExpired;
1903                     }
1904                     else if (property.first == "UserGroups")
1905                     {
1906                         const std::vector<std::string>* userGroups =
1907                             std::get_if<std::vector<std::string>>(
1908                                 &property.second);
1909                         if (userGroups == nullptr)
1910                         {
1911                             BMCWEB_LOG_ERROR
1912                                 << "userGroups wasn't a string vector";
1913                             messages::internalError(asyncResp->res);
1914                             return;
1915                         }
1916                         if (!translateUserGroup(*userGroups, asyncResp->res))
1917                         {
1918                             BMCWEB_LOG_ERROR << "userGroups mapping failed";
1919                             messages::internalError(asyncResp->res);
1920                             return;
1921                         }
1922                     }
1923                 }
1924             }
1925         }
1926 
1927         asyncResp->res.jsonValue["@odata.id"] =
1928             "/redfish/v1/AccountService/Accounts/" + accountName;
1929         asyncResp->res.jsonValue["Id"] = accountName;
1930         asyncResp->res.jsonValue["UserName"] = accountName;
1931         },
1932         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1933         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1934 }
1935 
1936 inline void
1937     handleAccountDelete(App& app, const crow::Request& req,
1938                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1939                         const std::string& username)
1940 {
1941     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1942     {
1943         return;
1944     }
1945 
1946 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1947     // If authentication is disabled, there are no user accounts
1948     messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
1949     return;
1950 
1951 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1952     sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1953     tempObjPath /= username;
1954     const std::string userPath(tempObjPath);
1955 
1956     crow::connections::systemBus->async_method_call(
1957         [asyncResp, username](const boost::system::error_code& ec) {
1958         if (ec)
1959         {
1960             messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1961                                        username);
1962             return;
1963         }
1964 
1965         messages::accountRemoved(asyncResp->res);
1966         },
1967         "xyz.openbmc_project.User.Manager", userPath,
1968         "xyz.openbmc_project.Object.Delete", "Delete");
1969 }
1970 
1971 inline void
1972     handleAccountPatch(App& app, const crow::Request& req,
1973                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1974                        const std::string& username)
1975 {
1976     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1977     {
1978         return;
1979     }
1980 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1981     // If authentication is disabled, there are no user accounts
1982     messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
1983     return;
1984 
1985 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1986     std::optional<std::string> newUserName;
1987     std::optional<std::string> password;
1988     std::optional<bool> enabled;
1989     std::optional<std::string> roleId;
1990     std::optional<bool> locked;
1991 
1992     if (req.session == nullptr)
1993     {
1994         messages::internalError(asyncResp->res);
1995         return;
1996     }
1997 
1998     Privileges effectiveUserPrivileges =
1999         redfish::getUserPrivileges(*req.session);
2000     Privileges configureUsers = {"ConfigureUsers"};
2001     bool userHasConfigureUsers =
2002         effectiveUserPrivileges.isSupersetOf(configureUsers);
2003     if (userHasConfigureUsers)
2004     {
2005         // Users with ConfigureUsers can modify for all users
2006         if (!json_util::readJsonPatch(req, asyncResp->res, "UserName",
2007                                       newUserName, "Password", password,
2008                                       "RoleId", roleId, "Enabled", enabled,
2009                                       "Locked", locked))
2010         {
2011             return;
2012         }
2013     }
2014     else
2015     {
2016         // ConfigureSelf accounts can only modify their own account
2017         if (username != req.session->username)
2018         {
2019             messages::insufficientPrivilege(asyncResp->res);
2020             return;
2021         }
2022 
2023         // ConfigureSelf accounts can only modify their password
2024         if (!json_util::readJsonPatch(req, asyncResp->res, "Password",
2025                                       password))
2026         {
2027             return;
2028         }
2029     }
2030 
2031     // if user name is not provided in the patch method or if it
2032     // matches the user name in the URI, then we are treating it as
2033     // updating user properties other then username. If username
2034     // provided doesn't match the URI, then we are treating this as
2035     // user rename request.
2036     if (!newUserName || (newUserName.value() == username))
2037     {
2038         updateUserProperties(asyncResp, username, password, enabled, roleId,
2039                              locked);
2040         return;
2041     }
2042     crow::connections::systemBus->async_method_call(
2043         [asyncResp, username, password(std::move(password)),
2044          roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)},
2045          locked](const boost::system::error_code& ec, sdbusplus::message_t& m) {
2046         if (ec)
2047         {
2048             userErrorMessageHandler(m.get_error(), asyncResp, newUser,
2049                                     username);
2050             return;
2051         }
2052 
2053         updateUserProperties(asyncResp, newUser, password, enabled, roleId,
2054                              locked);
2055         },
2056         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
2057         "xyz.openbmc_project.User.Manager", "RenameUser", username,
2058         *newUserName);
2059 }
2060 
2061 inline void requestAccountServiceRoutes(App& app)
2062 {
2063     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2064         .privileges(redfish::privileges::headAccountService)
2065         .methods(boost::beast::http::verb::head)(
2066             std::bind_front(handleAccountServiceHead, std::ref(app)));
2067 
2068     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2069         .privileges(redfish::privileges::getAccountService)
2070         .methods(boost::beast::http::verb::get)(
2071             std::bind_front(handleAccountServiceGet, std::ref(app)));
2072 
2073     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2074         .privileges(redfish::privileges::patchAccountService)
2075         .methods(boost::beast::http::verb::patch)(
2076             std::bind_front(handleAccountServicePatch, std::ref(app)));
2077 
2078     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2079         .privileges(redfish::privileges::headManagerAccountCollection)
2080         .methods(boost::beast::http::verb::head)(
2081             std::bind_front(handleAccountCollectionHead, std::ref(app)));
2082 
2083     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2084         .privileges(redfish::privileges::getManagerAccountCollection)
2085         .methods(boost::beast::http::verb::get)(
2086             std::bind_front(handleAccountCollectionGet, std::ref(app)));
2087 
2088     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2089         .privileges(redfish::privileges::postManagerAccountCollection)
2090         .methods(boost::beast::http::verb::post)(
2091             std::bind_front(handleAccountCollectionPost, std::ref(app)));
2092 
2093     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2094         .privileges(redfish::privileges::headManagerAccount)
2095         .methods(boost::beast::http::verb::head)(
2096             std::bind_front(handleAccountHead, std::ref(app)));
2097 
2098     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2099         .privileges(redfish::privileges::getManagerAccount)
2100         .methods(boost::beast::http::verb::get)(
2101             std::bind_front(handleAccountGet, std::ref(app)));
2102 
2103     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2104         // TODO this privilege should be using the generated endpoints, but
2105         // because of the special handling of ConfigureSelf, it's not able to
2106         // yet
2107         .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}})
2108         .methods(boost::beast::http::verb::patch)(
2109             std::bind_front(handleAccountPatch, std::ref(app)));
2110 
2111     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2112         .privileges(redfish::privileges::deleteManagerAccount)
2113         .methods(boost::beast::http::verb::delete_)(
2114             std::bind_front(handleAccountDelete, std::ref(app)));
2115 }
2116 
2117 } // namespace redfish
2118