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