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::message& 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::message& 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,
807          serviceAddressList](const boost::system::error_code& ec,
808                              sdbusplus::message::message& msg) {
809         if (ec)
810         {
811             const sd_bus_error* dbusError = msg.get_error();
812             if ((dbusError != nullptr) &&
813                 (dbusError->name ==
814                  std::string_view(
815                      "xyz.openbmc_project.Common.Error.InvalidArgument")))
816             {
817                 BMCWEB_LOG_WARNING
818                     << "Error Occurred in updating the service address";
819                 messages::propertyValueIncorrect(asyncResp->res,
820                                                  "ServiceAddresses",
821                                                  serviceAddressList.front());
822                 return;
823             }
824             messages::internalError(asyncResp->res);
825             return;
826         }
827         std::vector<std::string> modifiedserviceAddressList = {
828             serviceAddressList.front()};
829         asyncResp->res.jsonValue[ldapServerElementName]["ServiceAddresses"] =
830             modifiedserviceAddressList;
831         if ((serviceAddressList).size() > 1)
832         {
833             messages::propertyValueModified(asyncResp->res, "ServiceAddresses",
834                                             serviceAddressList.front());
835         }
836         BMCWEB_LOG_DEBUG << "Updated the service address";
837         },
838         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
839         ldapConfigInterface, "LDAPServerURI",
840         dbus::utility::DbusVariantType(serviceAddressList.front()));
841 }
842 /**
843  * @brief updates the LDAP Bind DN and updates the
844           json response with the new value.
845  * @param username name of the user which needs to be updated.
846  * @param asyncResp pointer to the JSON response
847  * @param ldapServerElementName Type of LDAP
848  server(openLDAP/ActiveDirectory)
849  */
850 
851 inline void
852     handleUserNamePatch(const std::string& username,
853                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
854                         const std::string& ldapServerElementName,
855                         const std::string& ldapConfigObject)
856 {
857     crow::connections::systemBus->async_method_call(
858         [asyncResp, username,
859          ldapServerElementName](const boost::system::error_code& ec) {
860         if (ec)
861         {
862             BMCWEB_LOG_DEBUG << "Error occurred in updating the username";
863             messages::internalError(asyncResp->res);
864             return;
865         }
866         asyncResp->res.jsonValue[ldapServerElementName]["Authentication"]
867                                 ["Username"] = username;
868         BMCWEB_LOG_DEBUG << "Updated the username";
869         },
870         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
871         ldapConfigInterface, "LDAPBindDN",
872         dbus::utility::DbusVariantType(username));
873 }
874 
875 /**
876  * @brief updates the LDAP password
877  * @param password : ldap password which needs to be updated.
878  * @param asyncResp pointer to the JSON response
879  * @param ldapServerElementName Type of LDAP
880  *        server(openLDAP/ActiveDirectory)
881  */
882 
883 inline void
884     handlePasswordPatch(const std::string& password,
885                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
886                         const std::string& ldapServerElementName,
887                         const std::string& ldapConfigObject)
888 {
889     crow::connections::systemBus->async_method_call(
890         [asyncResp, password,
891          ldapServerElementName](const boost::system::error_code& ec) {
892         if (ec)
893         {
894             BMCWEB_LOG_DEBUG << "Error occurred in updating the password";
895             messages::internalError(asyncResp->res);
896             return;
897         }
898         asyncResp->res.jsonValue[ldapServerElementName]["Authentication"]
899                                 ["Password"] = "";
900         BMCWEB_LOG_DEBUG << "Updated the password";
901         },
902         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
903         ldapConfigInterface, "LDAPBindDNPassword",
904         dbus::utility::DbusVariantType(password));
905 }
906 
907 /**
908  * @brief updates the LDAP BaseDN and updates the
909           json response with the new value.
910  * @param baseDNList baseDN list which needs to be updated.
911  * @param asyncResp pointer to the JSON response
912  * @param ldapServerElementName Type of LDAP
913  server(openLDAP/ActiveDirectory)
914  */
915 
916 inline void
917     handleBaseDNPatch(const std::vector<std::string>& baseDNList,
918                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
919                       const std::string& ldapServerElementName,
920                       const std::string& ldapConfigObject)
921 {
922     crow::connections::systemBus->async_method_call(
923         [asyncResp, baseDNList,
924          ldapServerElementName](const boost::system::error_code& ec,
925                                 const sdbusplus::message::message& msg) {
926         if (ec)
927         {
928             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the base DN";
929             const sd_bus_error* dbusError = msg.get_error();
930             if ((dbusError != nullptr) &&
931                 (dbusError->name ==
932                  std::string_view(
933                      "xyz.openbmc_project.Common.Error.InvalidArgument")))
934             {
935                 messages::propertyValueIncorrect(asyncResp->res,
936                                                  "BaseDistinguishedNames",
937                                                  baseDNList.front());
938                 return;
939             }
940             messages::internalError(asyncResp->res);
941             return;
942         }
943         auto& serverTypeJson = asyncResp->res.jsonValue[ldapServerElementName];
944         auto& searchSettingsJson =
945             serverTypeJson["LDAPService"]["SearchSettings"];
946         std::vector<std::string> modifiedBaseDNList = {baseDNList.front()};
947         searchSettingsJson["BaseDistinguishedNames"] = modifiedBaseDNList;
948         if (baseDNList.size() > 1)
949         {
950             messages::propertyValueModified(
951                 asyncResp->res, "BaseDistinguishedNames", baseDNList.front());
952         }
953         BMCWEB_LOG_DEBUG << "Updated the base DN";
954         },
955         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
956         ldapConfigInterface, "LDAPBaseDN",
957         dbus::utility::DbusVariantType(baseDNList.front()));
958 }
959 /**
960  * @brief updates the LDAP user name attribute and updates the
961           json response with the new value.
962  * @param userNameAttribute attribute to be updated.
963  * @param asyncResp pointer to the JSON response
964  * @param ldapServerElementName Type of LDAP
965  server(openLDAP/ActiveDirectory)
966  */
967 
968 inline void
969     handleUserNameAttrPatch(const std::string& userNameAttribute,
970                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
971                             const std::string& ldapServerElementName,
972                             const std::string& ldapConfigObject)
973 {
974     crow::connections::systemBus->async_method_call(
975         [asyncResp, userNameAttribute,
976          ldapServerElementName](const boost::system::error_code& ec) {
977         if (ec)
978         {
979             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the "
980                                 "username attribute";
981             messages::internalError(asyncResp->res);
982             return;
983         }
984         auto& serverTypeJson = asyncResp->res.jsonValue[ldapServerElementName];
985         auto& searchSettingsJson =
986             serverTypeJson["LDAPService"]["SearchSettings"];
987         searchSettingsJson["UsernameAttribute"] = userNameAttribute;
988         BMCWEB_LOG_DEBUG << "Updated the user name attr.";
989         },
990         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
991         ldapConfigInterface, "UserNameAttribute",
992         dbus::utility::DbusVariantType(userNameAttribute));
993 }
994 /**
995  * @brief updates the LDAP group attribute and updates the
996           json response with the new value.
997  * @param groupsAttribute attribute to be updated.
998  * @param asyncResp pointer to the JSON response
999  * @param ldapServerElementName Type of LDAP
1000  server(openLDAP/ActiveDirectory)
1001  */
1002 
1003 inline void handleGroupNameAttrPatch(
1004     const std::string& groupsAttribute,
1005     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1006     const std::string& ldapServerElementName,
1007     const std::string& ldapConfigObject)
1008 {
1009     crow::connections::systemBus->async_method_call(
1010         [asyncResp, groupsAttribute,
1011          ldapServerElementName](const boost::system::error_code& ec) {
1012         if (ec)
1013         {
1014             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the "
1015                                 "groupname attribute";
1016             messages::internalError(asyncResp->res);
1017             return;
1018         }
1019         auto& serverTypeJson = asyncResp->res.jsonValue[ldapServerElementName];
1020         auto& searchSettingsJson =
1021             serverTypeJson["LDAPService"]["SearchSettings"];
1022         searchSettingsJson["GroupsAttribute"] = groupsAttribute;
1023         BMCWEB_LOG_DEBUG << "Updated the groupname attr";
1024         },
1025         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
1026         ldapConfigInterface, "GroupNameAttribute",
1027         dbus::utility::DbusVariantType(groupsAttribute));
1028 }
1029 /**
1030  * @brief updates the LDAP service enable and updates the
1031           json response with the new value.
1032  * @param input JSON data.
1033  * @param asyncResp pointer to the JSON response
1034  * @param ldapServerElementName Type of LDAP
1035  server(openLDAP/ActiveDirectory)
1036  */
1037 
1038 inline void handleServiceEnablePatch(
1039     bool serviceEnabled, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1040     const std::string& ldapServerElementName,
1041     const std::string& ldapConfigObject)
1042 {
1043     crow::connections::systemBus->async_method_call(
1044         [asyncResp, serviceEnabled,
1045          ldapServerElementName](const boost::system::error_code& ec) {
1046         if (ec)
1047         {
1048             BMCWEB_LOG_DEBUG << "Error Occurred in Updating the service enable";
1049             messages::internalError(asyncResp->res);
1050             return;
1051         }
1052         asyncResp->res.jsonValue[ldapServerElementName]["ServiceEnabled"] =
1053             serviceEnabled;
1054         BMCWEB_LOG_DEBUG << "Updated Service enable = " << serviceEnabled;
1055         },
1056         ldapDbusService, ldapConfigObject, propertyInterface, "Set",
1057         ldapEnableInterface, "Enabled",
1058         dbus::utility::DbusVariantType(serviceEnabled));
1059 }
1060 
1061 inline void
1062     handleAuthMethodsPatch(nlohmann::json& input,
1063                            const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1064 {
1065     std::optional<bool> basicAuth;
1066     std::optional<bool> cookie;
1067     std::optional<bool> sessionToken;
1068     std::optional<bool> xToken;
1069     std::optional<bool> tls;
1070 
1071     if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth,
1072                              "Cookie", cookie, "SessionToken", sessionToken,
1073                              "XToken", xToken, "TLS", tls))
1074     {
1075         BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag";
1076         return;
1077     }
1078 
1079     // Make a copy of methods configuration
1080     persistent_data::AuthConfigMethods authMethodsConfig =
1081         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1082 
1083     if (basicAuth)
1084     {
1085 #ifndef BMCWEB_ENABLE_BASIC_AUTHENTICATION
1086         messages::actionNotSupported(
1087             asyncResp->res,
1088             "Setting BasicAuth when basic-auth feature is disabled");
1089         return;
1090 #endif
1091         authMethodsConfig.basic = *basicAuth;
1092     }
1093 
1094     if (cookie)
1095     {
1096 #ifndef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
1097         messages::actionNotSupported(
1098             asyncResp->res,
1099             "Setting Cookie when cookie-auth feature is disabled");
1100         return;
1101 #endif
1102         authMethodsConfig.cookie = *cookie;
1103     }
1104 
1105     if (sessionToken)
1106     {
1107 #ifndef BMCWEB_ENABLE_SESSION_AUTHENTICATION
1108         messages::actionNotSupported(
1109             asyncResp->res,
1110             "Setting SessionToken when session-auth feature is disabled");
1111         return;
1112 #endif
1113         authMethodsConfig.sessionToken = *sessionToken;
1114     }
1115 
1116     if (xToken)
1117     {
1118 #ifndef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
1119         messages::actionNotSupported(
1120             asyncResp->res,
1121             "Setting XToken when xtoken-auth feature is disabled");
1122         return;
1123 #endif
1124         authMethodsConfig.xtoken = *xToken;
1125     }
1126 
1127     if (tls)
1128     {
1129 #ifndef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
1130         messages::actionNotSupported(
1131             asyncResp->res,
1132             "Setting TLS when mutual-tls-auth feature is disabled");
1133         return;
1134 #endif
1135         authMethodsConfig.tls = *tls;
1136     }
1137 
1138     if (!authMethodsConfig.basic && !authMethodsConfig.cookie &&
1139         !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken &&
1140         !authMethodsConfig.tls)
1141     {
1142         // Do not allow user to disable everything
1143         messages::actionNotSupported(asyncResp->res,
1144                                      "of disabling all available methods");
1145         return;
1146     }
1147 
1148     persistent_data::SessionStore::getInstance().updateAuthMethodsConfig(
1149         authMethodsConfig);
1150     // Save configuration immediately
1151     persistent_data::getConfig().writeData();
1152 
1153     messages::success(asyncResp->res);
1154 }
1155 
1156 /**
1157  * @brief Get the required values from the given JSON, validates the
1158  *        value and create the LDAP config object.
1159  * @param input JSON data
1160  * @param asyncResp pointer to the JSON response
1161  * @param serverType Type of LDAP server(openLDAP/ActiveDirectory)
1162  */
1163 
1164 inline void handleLDAPPatch(nlohmann::json& input,
1165                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1166                             const std::string& serverType)
1167 {
1168     std::string dbusObjectPath;
1169     if (serverType == "ActiveDirectory")
1170     {
1171         dbusObjectPath = adConfigObject;
1172     }
1173     else if (serverType == "LDAP")
1174     {
1175         dbusObjectPath = ldapConfigObjectName;
1176     }
1177     else
1178     {
1179         return;
1180     }
1181 
1182     std::optional<nlohmann::json> authentication;
1183     std::optional<nlohmann::json> ldapService;
1184     std::optional<std::vector<std::string>> serviceAddressList;
1185     std::optional<bool> serviceEnabled;
1186     std::optional<std::vector<std::string>> baseDNList;
1187     std::optional<std::string> userNameAttribute;
1188     std::optional<std::string> groupsAttribute;
1189     std::optional<std::string> userName;
1190     std::optional<std::string> password;
1191     std::optional<std::vector<nlohmann::json>> remoteRoleMapData;
1192 
1193     if (!json_util::readJson(input, asyncResp->res, "Authentication",
1194                              authentication, "LDAPService", ldapService,
1195                              "ServiceAddresses", serviceAddressList,
1196                              "ServiceEnabled", serviceEnabled,
1197                              "RemoteRoleMapping", remoteRoleMapData))
1198     {
1199         return;
1200     }
1201 
1202     if (authentication)
1203     {
1204         parseLDAPAuthenticationJson(*authentication, asyncResp, userName,
1205                                     password);
1206     }
1207     if (ldapService)
1208     {
1209         parseLDAPServiceJson(*ldapService, asyncResp, baseDNList,
1210                              userNameAttribute, groupsAttribute);
1211     }
1212     if (serviceAddressList)
1213     {
1214         if (serviceAddressList->empty())
1215         {
1216             messages::propertyValueNotInList(
1217                 asyncResp->res, *serviceAddressList, "ServiceAddress");
1218             return;
1219         }
1220     }
1221     if (baseDNList)
1222     {
1223         if (baseDNList->empty())
1224         {
1225             messages::propertyValueNotInList(asyncResp->res, *baseDNList,
1226                                              "BaseDistinguishedNames");
1227             return;
1228         }
1229     }
1230 
1231     // nothing to update, then return
1232     if (!userName && !password && !serviceAddressList && !baseDNList &&
1233         !userNameAttribute && !groupsAttribute && !serviceEnabled &&
1234         !remoteRoleMapData)
1235     {
1236         return;
1237     }
1238 
1239     // Get the existing resource first then keep modifying
1240     // whenever any property gets updated.
1241     getLDAPConfigData(
1242         serverType,
1243         [asyncResp, userName, password, baseDNList, userNameAttribute,
1244          groupsAttribute, serviceAddressList, serviceEnabled, dbusObjectPath,
1245          remoteRoleMapData](bool success, const LDAPConfigData& confData,
1246                             const std::string& serverT) {
1247         if (!success)
1248         {
1249             messages::internalError(asyncResp->res);
1250             return;
1251         }
1252         parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverT);
1253         if (confData.serviceEnabled)
1254         {
1255             // Disable the service first and update the rest of
1256             // the properties.
1257             handleServiceEnablePatch(false, asyncResp, serverT, dbusObjectPath);
1258         }
1259 
1260         if (serviceAddressList)
1261         {
1262             handleServiceAddressPatch(*serviceAddressList, asyncResp, serverT,
1263                                       dbusObjectPath);
1264         }
1265         if (userName)
1266         {
1267             handleUserNamePatch(*userName, asyncResp, serverT, dbusObjectPath);
1268         }
1269         if (password)
1270         {
1271             handlePasswordPatch(*password, asyncResp, serverT, dbusObjectPath);
1272         }
1273 
1274         if (baseDNList)
1275         {
1276             handleBaseDNPatch(*baseDNList, asyncResp, serverT, dbusObjectPath);
1277         }
1278         if (userNameAttribute)
1279         {
1280             handleUserNameAttrPatch(*userNameAttribute, asyncResp, serverT,
1281                                     dbusObjectPath);
1282         }
1283         if (groupsAttribute)
1284         {
1285             handleGroupNameAttrPatch(*groupsAttribute, asyncResp, serverT,
1286                                      dbusObjectPath);
1287         }
1288         if (serviceEnabled)
1289         {
1290             // if user has given the value as true then enable
1291             // the service. if user has given false then no-op
1292             // as service is already stopped.
1293             if (*serviceEnabled)
1294             {
1295                 handleServiceEnablePatch(*serviceEnabled, asyncResp, serverT,
1296                                          dbusObjectPath);
1297             }
1298         }
1299         else
1300         {
1301             // if user has not given the service enabled value
1302             // then revert it to the same state as it was
1303             // before.
1304             handleServiceEnablePatch(confData.serviceEnabled, asyncResp,
1305                                      serverT, dbusObjectPath);
1306         }
1307 
1308         if (remoteRoleMapData)
1309         {
1310             handleRoleMapPatch(asyncResp, confData.groupRoleList, serverT,
1311                                *remoteRoleMapData);
1312         }
1313         });
1314 }
1315 
1316 inline void updateUserProperties(
1317     std::shared_ptr<bmcweb::AsyncResp> asyncResp, const std::string& username,
1318     const std::optional<std::string>& password,
1319     const std::optional<bool>& enabled,
1320     const std::optional<std::string>& roleId, const std::optional<bool>& locked,
1321     std::optional<std::vector<std::string>> accountTypes, bool userSelf)
1322 {
1323     sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1324     tempObjPath /= username;
1325     std::string dbusObjectPath(tempObjPath);
1326 
1327     dbus::utility::checkDbusPathExists(
1328         dbusObjectPath, [dbusObjectPath, username, password, roleId, enabled,
1329                          locked, accountTypes(std::move(accountTypes)),
1330                          userSelf, asyncResp{std::move(asyncResp)}](int rc) {
1331             if (rc <= 0)
1332             {
1333                 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1334                                            username);
1335                 return;
1336             }
1337 
1338             if (password)
1339             {
1340                 int retval = pamUpdatePassword(username, *password);
1341 
1342                 if (retval == PAM_USER_UNKNOWN)
1343                 {
1344                     messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1345                                                username);
1346                 }
1347                 else if (retval == PAM_AUTHTOK_ERR)
1348                 {
1349                     // If password is invalid
1350                     messages::propertyValueFormatError(asyncResp->res,
1351                                                        *password, "Password");
1352                     BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1353                 }
1354                 else if (retval != PAM_SUCCESS)
1355                 {
1356                     messages::internalError(asyncResp->res);
1357                     return;
1358                 }
1359                 else
1360                 {
1361                     messages::success(asyncResp->res);
1362                 }
1363             }
1364 
1365             if (enabled)
1366             {
1367                 crow::connections::systemBus->async_method_call(
1368                     [asyncResp](const boost::system::error_code& ec) {
1369                 if (ec)
1370                 {
1371                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1372                     messages::internalError(asyncResp->res);
1373                     return;
1374                 }
1375                 messages::success(asyncResp->res);
1376                 return;
1377                     },
1378                     "xyz.openbmc_project.User.Manager", dbusObjectPath,
1379                     "org.freedesktop.DBus.Properties", "Set",
1380                     "xyz.openbmc_project.User.Attributes", "UserEnabled",
1381                     dbus::utility::DbusVariantType{*enabled});
1382             }
1383 
1384             if (roleId)
1385             {
1386                 std::string priv = getPrivilegeFromRoleId(*roleId);
1387                 if (priv.empty())
1388                 {
1389                     messages::propertyValueNotInList(asyncResp->res, true,
1390                                                      "Locked");
1391                     return;
1392                 }
1393 
1394                 crow::connections::systemBus->async_method_call(
1395                     [asyncResp](const boost::system::error_code& ec) {
1396                 if (ec)
1397                 {
1398                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1399                     messages::internalError(asyncResp->res);
1400                     return;
1401                 }
1402                 messages::success(asyncResp->res);
1403                     },
1404                     "xyz.openbmc_project.User.Manager", dbusObjectPath,
1405                     "org.freedesktop.DBus.Properties", "Set",
1406                     "xyz.openbmc_project.User.Attributes", "UserPrivilege",
1407                     dbus::utility::DbusVariantType{priv});
1408             }
1409 
1410             if (locked)
1411             {
1412                 // admin can unlock the account which is locked by
1413                 // successive authentication failures but admin should
1414                 // not be allowed to lock an account.
1415                 if (*locked)
1416                 {
1417                     messages::propertyValueNotInList(asyncResp->res, "true",
1418                                                      "Locked");
1419                     return;
1420                 }
1421 
1422                 crow::connections::systemBus->async_method_call(
1423                     [asyncResp](const boost::system::error_code& ec) {
1424                 if (ec)
1425                 {
1426                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1427                     messages::internalError(asyncResp->res);
1428                     return;
1429                 }
1430                 messages::success(asyncResp->res);
1431                 return;
1432                     },
1433                     "xyz.openbmc_project.User.Manager", dbusObjectPath,
1434                     "org.freedesktop.DBus.Properties", "Set",
1435                     "xyz.openbmc_project.User.Attributes",
1436                     "UserLockedForFailedAttempt",
1437                     dbus::utility::DbusVariantType{*locked});
1438             }
1439 
1440             if (accountTypes)
1441             {
1442                 patchAccountTypes(*accountTypes, asyncResp, dbusObjectPath,
1443                                   userSelf);
1444             }
1445         });
1446 }
1447 
1448 inline void handleAccountServiceHead(
1449     App& app, const crow::Request& req,
1450     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1451 {
1452     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1453     {
1454         return;
1455     }
1456     asyncResp->res.addHeader(
1457         boost::beast::http::field::link,
1458         "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby");
1459 }
1460 
1461 inline void
1462     handleAccountServiceGet(App& app, const crow::Request& req,
1463                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1464 {
1465     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1466     {
1467         return;
1468     }
1469 
1470     if (req.session == nullptr)
1471     {
1472         messages::internalError(asyncResp->res);
1473         return;
1474     }
1475 
1476     asyncResp->res.addHeader(
1477         boost::beast::http::field::link,
1478         "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby");
1479 
1480     const persistent_data::AuthConfigMethods& authMethodsConfig =
1481         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1482 
1483     nlohmann::json& json = asyncResp->res.jsonValue;
1484     json["@odata.id"] = "/redfish/v1/AccountService";
1485     json["@odata.type"] = "#AccountService."
1486                           "v1_10_0.AccountService";
1487     json["Id"] = "AccountService";
1488     json["Name"] = "Account Service";
1489     json["Description"] = "Account Service";
1490     json["ServiceEnabled"] = true;
1491     json["MaxPasswordLength"] = 20;
1492     json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts";
1493     json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
1494     json["Oem"]["OpenBMC"]["@odata.type"] =
1495         "#OpenBMCAccountService.v1_0_0.AccountService";
1496     json["Oem"]["OpenBMC"]["@odata.id"] =
1497         "/redfish/v1/AccountService#/Oem/OpenBMC";
1498     json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] =
1499         authMethodsConfig.basic;
1500     json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] =
1501         authMethodsConfig.sessionToken;
1502     json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken;
1503     json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie;
1504     json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls;
1505 
1506     // /redfish/v1/AccountService/LDAP/Certificates is something only
1507     // ConfigureManager can access then only display when the user has
1508     // permissions ConfigureManager
1509     Privileges effectiveUserPrivileges =
1510         redfish::getUserPrivileges(*req.session);
1511 
1512     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
1513                                          effectiveUserPrivileges))
1514     {
1515         asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] =
1516             "/redfish/v1/AccountService/LDAP/Certificates";
1517     }
1518     sdbusplus::asio::getAllProperties(
1519         *crow::connections::systemBus, "xyz.openbmc_project.User.Manager",
1520         "/xyz/openbmc_project/user", "xyz.openbmc_project.User.AccountPolicy",
1521         [asyncResp](const boost::system::error_code& ec,
1522                     const dbus::utility::DBusPropertiesMap& propertiesList) {
1523         if (ec)
1524         {
1525             messages::internalError(asyncResp->res);
1526             return;
1527         }
1528 
1529         BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
1530                          << "properties for AccountService";
1531 
1532         const uint8_t* minPasswordLength = nullptr;
1533         const uint32_t* accountUnlockTimeout = nullptr;
1534         const uint16_t* maxLoginAttemptBeforeLockout = nullptr;
1535 
1536         const bool success = sdbusplus::unpackPropertiesNoThrow(
1537             dbus_utils::UnpackErrorPrinter(), propertiesList,
1538             "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout",
1539             accountUnlockTimeout, "MaxLoginAttemptBeforeLockout",
1540             maxLoginAttemptBeforeLockout);
1541 
1542         if (!success)
1543         {
1544             messages::internalError(asyncResp->res);
1545             return;
1546         }
1547 
1548         if (minPasswordLength != nullptr)
1549         {
1550             asyncResp->res.jsonValue["MinPasswordLength"] = *minPasswordLength;
1551         }
1552 
1553         if (accountUnlockTimeout != nullptr)
1554         {
1555             asyncResp->res.jsonValue["AccountLockoutDuration"] =
1556                 *accountUnlockTimeout;
1557         }
1558 
1559         if (maxLoginAttemptBeforeLockout != nullptr)
1560         {
1561             asyncResp->res.jsonValue["AccountLockoutThreshold"] =
1562                 *maxLoginAttemptBeforeLockout;
1563         }
1564         });
1565 
1566     auto callback = [asyncResp](bool success, const LDAPConfigData& confData,
1567                                 const std::string& ldapType) {
1568         if (!success)
1569         {
1570             return;
1571         }
1572         parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1573     };
1574 
1575     getLDAPConfigData("LDAP", callback);
1576     getLDAPConfigData("ActiveDirectory", callback);
1577 }
1578 
1579 inline void handleAccountServicePatch(
1580     App& app, const crow::Request& req,
1581     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1582 {
1583     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1584     {
1585         return;
1586     }
1587     std::optional<uint32_t> unlockTimeout;
1588     std::optional<uint16_t> lockoutThreshold;
1589     std::optional<uint8_t> minPasswordLength;
1590     std::optional<uint16_t> maxPasswordLength;
1591     std::optional<nlohmann::json> ldapObject;
1592     std::optional<nlohmann::json> activeDirectoryObject;
1593     std::optional<nlohmann::json> oemObject;
1594 
1595     if (!json_util::readJsonPatch(
1596             req, asyncResp->res, "AccountLockoutDuration", unlockTimeout,
1597             "AccountLockoutThreshold", lockoutThreshold, "MaxPasswordLength",
1598             maxPasswordLength, "MinPasswordLength", minPasswordLength, "LDAP",
1599             ldapObject, "ActiveDirectory", activeDirectoryObject, "Oem",
1600             oemObject))
1601     {
1602         return;
1603     }
1604 
1605     if (minPasswordLength)
1606     {
1607         crow::connections::systemBus->async_method_call(
1608             [asyncResp](const boost::system::error_code& ec) {
1609             if (ec)
1610             {
1611                 messages::internalError(asyncResp->res);
1612                 return;
1613             }
1614             messages::success(asyncResp->res);
1615             },
1616             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1617             "org.freedesktop.DBus.Properties", "Set",
1618             "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength",
1619             dbus::utility::DbusVariantType(*minPasswordLength));
1620     }
1621 
1622     if (maxPasswordLength)
1623     {
1624         messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1625     }
1626 
1627     if (ldapObject)
1628     {
1629         handleLDAPPatch(*ldapObject, asyncResp, "LDAP");
1630     }
1631 
1632     if (std::optional<nlohmann::json> oemOpenBMCObject;
1633         oemObject && json_util::readJson(*oemObject, asyncResp->res, "OpenBMC",
1634                                          oemOpenBMCObject))
1635     {
1636         if (std::optional<nlohmann::json> authMethodsObject;
1637             oemOpenBMCObject &&
1638             json_util::readJson(*oemOpenBMCObject, asyncResp->res,
1639                                 "AuthMethods", authMethodsObject))
1640         {
1641             if (authMethodsObject)
1642             {
1643                 handleAuthMethodsPatch(*authMethodsObject, asyncResp);
1644             }
1645         }
1646     }
1647 
1648     if (activeDirectoryObject)
1649     {
1650         handleLDAPPatch(*activeDirectoryObject, asyncResp, "ActiveDirectory");
1651     }
1652 
1653     if (unlockTimeout)
1654     {
1655         crow::connections::systemBus->async_method_call(
1656             [asyncResp](const boost::system::error_code& ec) {
1657             if (ec)
1658             {
1659                 messages::internalError(asyncResp->res);
1660                 return;
1661             }
1662             messages::success(asyncResp->res);
1663             },
1664             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1665             "org.freedesktop.DBus.Properties", "Set",
1666             "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout",
1667             dbus::utility::DbusVariantType(*unlockTimeout));
1668     }
1669     if (lockoutThreshold)
1670     {
1671         crow::connections::systemBus->async_method_call(
1672             [asyncResp](const boost::system::error_code& ec) {
1673             if (ec)
1674             {
1675                 messages::internalError(asyncResp->res);
1676                 return;
1677             }
1678             messages::success(asyncResp->res);
1679             },
1680             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1681             "org.freedesktop.DBus.Properties", "Set",
1682             "xyz.openbmc_project.User.AccountPolicy",
1683             "MaxLoginAttemptBeforeLockout",
1684             dbus::utility::DbusVariantType(*lockoutThreshold));
1685     }
1686 }
1687 
1688 inline void handleAccountCollectionHead(
1689     App& app, const crow::Request& req,
1690     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1691 {
1692     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1693     {
1694         return;
1695     }
1696     asyncResp->res.addHeader(
1697         boost::beast::http::field::link,
1698         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1699 }
1700 
1701 inline void handleAccountCollectionGet(
1702     App& app, const crow::Request& req,
1703     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1704 {
1705     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1706     {
1707         return;
1708     }
1709 
1710     if (req.session == nullptr)
1711     {
1712         messages::internalError(asyncResp->res);
1713         return;
1714     }
1715 
1716     asyncResp->res.addHeader(
1717         boost::beast::http::field::link,
1718         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1719 
1720     asyncResp->res.jsonValue["@odata.id"] =
1721         "/redfish/v1/AccountService/Accounts";
1722     asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection."
1723                                               "ManagerAccountCollection";
1724     asyncResp->res.jsonValue["Name"] = "Accounts Collection";
1725     asyncResp->res.jsonValue["Description"] = "BMC User Accounts";
1726 
1727     Privileges effectiveUserPrivileges =
1728         redfish::getUserPrivileges(*req.session);
1729 
1730     std::string thisUser;
1731     if (req.session)
1732     {
1733         thisUser = req.session->username;
1734     }
1735     sdbusplus::message::object_path path("/xyz/openbmc_project/user");
1736     dbus::utility::getManagedObjects(
1737         "xyz.openbmc_project.User.Manager", path,
1738         [asyncResp, thisUser, effectiveUserPrivileges](
1739             const boost::system::error_code& ec,
1740             const dbus::utility::ManagedObjectType& users) {
1741         if (ec)
1742         {
1743             messages::internalError(asyncResp->res);
1744             return;
1745         }
1746 
1747         bool userCanSeeAllAccounts =
1748             effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"});
1749 
1750         bool userCanSeeSelf =
1751             effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"});
1752 
1753         nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
1754         memberArray = nlohmann::json::array();
1755 
1756         for (const auto& userpath : users)
1757         {
1758             std::string user = userpath.first.filename();
1759             if (user.empty())
1760             {
1761                 messages::internalError(asyncResp->res);
1762                 BMCWEB_LOG_ERROR << "Invalid firmware ID";
1763 
1764                 return;
1765             }
1766 
1767             // As clarified by Redfish here:
1768             // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1769             // Users without ConfigureUsers, only see their own
1770             // account. Users with ConfigureUsers, see all
1771             // accounts.
1772             if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf))
1773             {
1774                 nlohmann::json::object_t member;
1775                 member["@odata.id"] = "/redfish/v1/AccountService/Accounts/" +
1776                                       user;
1777                 memberArray.emplace_back(std::move(member));
1778             }
1779         }
1780         asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size();
1781         });
1782 }
1783 
1784 inline void processAfterCreateUser(
1785     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1786     const std::string& username, const std::string& password,
1787     const boost::system::error_code& ec, sdbusplus::message_t& m)
1788 {
1789     if (ec)
1790     {
1791         userErrorMessageHandler(m.get_error(), asyncResp, username, "");
1792         return;
1793     }
1794 
1795     if (pamUpdatePassword(username, password) != PAM_SUCCESS)
1796     {
1797         // At this point we have a user that's been
1798         // created, but the password set
1799         // failed.Something is wrong, so delete the user
1800         // that we've already created
1801         sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1802         tempObjPath /= username;
1803         const std::string userPath(tempObjPath);
1804 
1805         crow::connections::systemBus->async_method_call(
1806             [asyncResp, password](const boost::system::error_code& ec3) {
1807             if (ec3)
1808             {
1809                 messages::internalError(asyncResp->res);
1810                 return;
1811             }
1812 
1813             // If password is invalid
1814             messages::propertyValueFormatError(asyncResp->res, password,
1815                                                "Password");
1816             },
1817             "xyz.openbmc_project.User.Manager", userPath,
1818             "xyz.openbmc_project.Object.Delete", "Delete");
1819 
1820         BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1821         return;
1822     }
1823 
1824     messages::created(asyncResp->res);
1825     asyncResp->res.addHeader("Location",
1826                              "/redfish/v1/AccountService/Accounts/" + username);
1827 }
1828 
1829 inline void processAfterGetAllGroups(
1830     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1831     const std::string& username, const std::string& password,
1832     const std::optional<std::string>& roleId, std::optional<bool> enabled,
1833     std::optional<std::vector<std::string>> accountTypes,
1834     const std::vector<std::string>& allGroupsList)
1835 {
1836     std::vector<std::string> userGroups;
1837     std::vector<std::string> accountTypeUserGroups;
1838 
1839     // If user specified account types then convert them to unix user groups
1840     if (accountTypes)
1841     {
1842         if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes,
1843                                          accountTypeUserGroups))
1844         {
1845             // Problem in mapping Account Types to User Groups, Error already
1846             // logged.
1847             return;
1848         }
1849     }
1850 
1851     for (const auto& grp : allGroupsList)
1852     {
1853         // If user specified the account type then only accept groups which are
1854         // in the account types group list.
1855         if (!accountTypeUserGroups.empty())
1856         {
1857             bool found = false;
1858             for (const auto& grp1 : accountTypeUserGroups)
1859             {
1860                 if (grp == grp1)
1861                 {
1862                     found = true;
1863                     break;
1864                 }
1865             }
1866             if (!found)
1867             {
1868                 continue;
1869             }
1870         }
1871 
1872         // Console access is provided to the user who is a member of
1873         // hostconsole group and has a administrator role. So, set
1874         // hostconsole group only for the administrator.
1875         if ((grp == "hostconsole") && (roleId != "priv-admin"))
1876         {
1877             if (!accountTypeUserGroups.empty())
1878             {
1879                 BMCWEB_LOG_ERROR
1880                     << "Only administrator can get HostConsole access";
1881                 asyncResp->res.result(boost::beast::http::status::bad_request);
1882                 return;
1883             }
1884             continue;
1885         }
1886         userGroups.emplace_back(grp);
1887     }
1888 
1889     // Make sure user specified groups are valid. This is internal error because
1890     // it some inconsistencies between user manager and bmcweb.
1891     if (!accountTypeUserGroups.empty() &&
1892         accountTypeUserGroups.size() != userGroups.size())
1893     {
1894         messages::internalError(asyncResp->res);
1895         return;
1896     }
1897 
1898     crow::connections::systemBus->async_method_call(
1899         [asyncResp, username, password](const boost::system::error_code& ec2,
1900                                         sdbusplus::message_t& m) {
1901         processAfterCreateUser(asyncResp, username, password, ec2, m);
1902         },
1903         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1904         "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups,
1905         *roleId, *enabled);
1906 }
1907 
1908 inline void handleAccountCollectionPost(
1909     App& app, const crow::Request& req,
1910     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1911 {
1912     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1913     {
1914         return;
1915     }
1916     std::string username;
1917     std::string password;
1918     std::optional<std::string> roleId("User");
1919     std::optional<bool> enabled = true;
1920     std::optional<std::vector<std::string>> accountTypes;
1921     if (!json_util::readJsonPatch(
1922             req, asyncResp->res, "UserName", username, "Password", password,
1923             "RoleId", roleId, "Enabled", enabled, "AccountTypes", accountTypes))
1924     {
1925         return;
1926     }
1927 
1928     std::string priv = getPrivilegeFromRoleId(*roleId);
1929     if (priv.empty())
1930     {
1931         messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
1932         return;
1933     }
1934     roleId = priv;
1935 
1936     // Reading AllGroups property
1937     sdbusplus::asio::getProperty<std::vector<std::string>>(
1938         *crow::connections::systemBus, "xyz.openbmc_project.User.Manager",
1939         "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager",
1940         "AllGroups",
1941         [asyncResp, username, password{std::move(password)}, roleId, enabled,
1942          accountTypes](const boost::system::error_code& ec,
1943                        const std::vector<std::string>& allGroupsList) {
1944         if (ec)
1945         {
1946             BMCWEB_LOG_DEBUG << "ERROR with async_method_call";
1947             messages::internalError(asyncResp->res);
1948             return;
1949         }
1950 
1951         if (allGroupsList.empty())
1952         {
1953             messages::internalError(asyncResp->res);
1954             return;
1955         }
1956 
1957         processAfterGetAllGroups(asyncResp, username, password, roleId, enabled,
1958                                  accountTypes, allGroupsList);
1959         });
1960 }
1961 
1962 inline void
1963     handleAccountHead(App& app, const crow::Request& req,
1964                       const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1965                       const std::string& /*accountName*/)
1966 {
1967     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1968     {
1969         return;
1970     }
1971     asyncResp->res.addHeader(
1972         boost::beast::http::field::link,
1973         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1974 }
1975 
1976 inline void
1977     handleAccountGet(App& app, const crow::Request& req,
1978                      const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1979                      const std::string& accountName)
1980 {
1981     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1982     {
1983         return;
1984     }
1985     asyncResp->res.addHeader(
1986         boost::beast::http::field::link,
1987         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1988 
1989 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1990     // If authentication is disabled, there are no user accounts
1991     messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName);
1992     return;
1993 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
1994 
1995     if (req.session == nullptr)
1996     {
1997         messages::internalError(asyncResp->res);
1998         return;
1999     }
2000     if (req.session->username != accountName)
2001     {
2002         // At this point we've determined that the user is trying to
2003         // modify a user that isn't them.  We need to verify that they
2004         // have permissions to modify other users, so re-run the auth
2005         // check with the same permissions, minus ConfigureSelf.
2006         Privileges effectiveUserPrivileges =
2007             redfish::getUserPrivileges(*req.session);
2008         Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers",
2009                                                          "ConfigureManager"};
2010         if (!effectiveUserPrivileges.isSupersetOf(
2011                 requiredPermissionsToChangeNonSelf))
2012         {
2013             BMCWEB_LOG_DEBUG << "GET Account denied access";
2014             messages::insufficientPrivilege(asyncResp->res);
2015             return;
2016         }
2017     }
2018 
2019     sdbusplus::message::object_path path("/xyz/openbmc_project/user");
2020     dbus::utility::getManagedObjects(
2021         "xyz.openbmc_project.User.Manager", path,
2022         [asyncResp,
2023          accountName](const boost::system::error_code& ec,
2024                       const dbus::utility::ManagedObjectType& users) {
2025         if (ec)
2026         {
2027             messages::internalError(asyncResp->res);
2028             return;
2029         }
2030         const auto userIt = std::find_if(
2031             users.begin(), users.end(),
2032             [accountName](
2033                 const std::pair<sdbusplus::message::object_path,
2034                                 dbus::utility::DBusInteracesMap>& user) {
2035             return accountName == user.first.filename();
2036             });
2037 
2038         if (userIt == users.end())
2039         {
2040             messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2041                                        accountName);
2042             return;
2043         }
2044 
2045         asyncResp->res.jsonValue["@odata.type"] =
2046             "#ManagerAccount.v1_7_0.ManagerAccount";
2047         asyncResp->res.jsonValue["Name"] = "User Account";
2048         asyncResp->res.jsonValue["Description"] = "User Account";
2049         asyncResp->res.jsonValue["Password"] = nullptr;
2050         asyncResp->res.jsonValue["StrictAccountTypes"] = true;
2051 
2052         for (const auto& interface : userIt->second)
2053         {
2054             if (interface.first == "xyz.openbmc_project.User.Attributes")
2055             {
2056                 for (const auto& property : interface.second)
2057                 {
2058                     if (property.first == "UserEnabled")
2059                     {
2060                         const bool* userEnabled =
2061                             std::get_if<bool>(&property.second);
2062                         if (userEnabled == nullptr)
2063                         {
2064                             BMCWEB_LOG_ERROR << "UserEnabled wasn't a bool";
2065                             messages::internalError(asyncResp->res);
2066                             return;
2067                         }
2068                         asyncResp->res.jsonValue["Enabled"] = *userEnabled;
2069                     }
2070                     else if (property.first == "UserLockedForFailedAttempt")
2071                     {
2072                         const bool* userLocked =
2073                             std::get_if<bool>(&property.second);
2074                         if (userLocked == nullptr)
2075                         {
2076                             BMCWEB_LOG_ERROR << "UserLockedForF"
2077                                                 "ailedAttempt "
2078                                                 "wasn't a bool";
2079                             messages::internalError(asyncResp->res);
2080                             return;
2081                         }
2082                         asyncResp->res.jsonValue["Locked"] = *userLocked;
2083                         asyncResp->res
2084                             .jsonValue["Locked@Redfish.AllowableValues"] = {
2085                             "false"}; // can only unlock accounts
2086                     }
2087                     else if (property.first == "UserPrivilege")
2088                     {
2089                         const std::string* userPrivPtr =
2090                             std::get_if<std::string>(&property.second);
2091                         if (userPrivPtr == nullptr)
2092                         {
2093                             BMCWEB_LOG_ERROR << "UserPrivilege wasn't a "
2094                                                 "string";
2095                             messages::internalError(asyncResp->res);
2096                             return;
2097                         }
2098                         std::string role = getRoleIdFromPrivilege(*userPrivPtr);
2099                         if (role.empty())
2100                         {
2101                             BMCWEB_LOG_ERROR << "Invalid user role";
2102                             messages::internalError(asyncResp->res);
2103                             return;
2104                         }
2105                         asyncResp->res.jsonValue["RoleId"] = role;
2106 
2107                         nlohmann::json& roleEntry =
2108                             asyncResp->res.jsonValue["Links"]["Role"];
2109                         roleEntry["@odata.id"] =
2110                             "/redfish/v1/AccountService/Roles/" + role;
2111                     }
2112                     else if (property.first == "UserPasswordExpired")
2113                     {
2114                         const bool* userPasswordExpired =
2115                             std::get_if<bool>(&property.second);
2116                         if (userPasswordExpired == nullptr)
2117                         {
2118                             BMCWEB_LOG_ERROR
2119                                 << "UserPasswordExpired wasn't a bool";
2120                             messages::internalError(asyncResp->res);
2121                             return;
2122                         }
2123                         asyncResp->res.jsonValue["PasswordChangeRequired"] =
2124                             *userPasswordExpired;
2125                     }
2126                     else if (property.first == "UserGroups")
2127                     {
2128                         const std::vector<std::string>* userGroups =
2129                             std::get_if<std::vector<std::string>>(
2130                                 &property.second);
2131                         if (userGroups == nullptr)
2132                         {
2133                             BMCWEB_LOG_ERROR
2134                                 << "userGroups wasn't a string vector";
2135                             messages::internalError(asyncResp->res);
2136                             return;
2137                         }
2138                         if (!translateUserGroup(*userGroups, asyncResp->res))
2139                         {
2140                             BMCWEB_LOG_ERROR << "userGroups mapping failed";
2141                             messages::internalError(asyncResp->res);
2142                             return;
2143                         }
2144                     }
2145                 }
2146             }
2147         }
2148 
2149         asyncResp->res.jsonValue["@odata.id"] =
2150             "/redfish/v1/AccountService/Accounts/" + accountName;
2151         asyncResp->res.jsonValue["Id"] = accountName;
2152         asyncResp->res.jsonValue["UserName"] = accountName;
2153         });
2154 }
2155 
2156 inline void
2157     handleAccountDelete(App& app, const crow::Request& req,
2158                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2159                         const std::string& username)
2160 {
2161     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2162     {
2163         return;
2164     }
2165 
2166 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
2167     // If authentication is disabled, there are no user accounts
2168     messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2169     return;
2170 
2171 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
2172     sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
2173     tempObjPath /= username;
2174     const std::string userPath(tempObjPath);
2175 
2176     crow::connections::systemBus->async_method_call(
2177         [asyncResp, username](const boost::system::error_code& ec) {
2178         if (ec)
2179         {
2180             messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2181                                        username);
2182             return;
2183         }
2184 
2185         messages::accountRemoved(asyncResp->res);
2186         },
2187         "xyz.openbmc_project.User.Manager", userPath,
2188         "xyz.openbmc_project.Object.Delete", "Delete");
2189 }
2190 
2191 inline void
2192     handleAccountPatch(App& app, const crow::Request& req,
2193                        const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2194                        const std::string& username)
2195 {
2196     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2197     {
2198         return;
2199     }
2200 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION
2201     // If authentication is disabled, there are no user accounts
2202     messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2203     return;
2204 
2205 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION
2206     std::optional<std::string> newUserName;
2207     std::optional<std::string> password;
2208     std::optional<bool> enabled;
2209     std::optional<std::string> roleId;
2210     std::optional<bool> locked;
2211     std::optional<std::vector<std::string>> accountTypes;
2212 
2213     bool userSelf = (username == req.session->username);
2214 
2215     if (req.session == nullptr)
2216     {
2217         messages::internalError(asyncResp->res);
2218         return;
2219     }
2220 
2221     Privileges effectiveUserPrivileges =
2222         redfish::getUserPrivileges(*req.session);
2223     Privileges configureUsers = {"ConfigureUsers"};
2224     bool userHasConfigureUsers =
2225         effectiveUserPrivileges.isSupersetOf(configureUsers);
2226     if (userHasConfigureUsers)
2227     {
2228         // Users with ConfigureUsers can modify for all users
2229         if (!json_util::readJsonPatch(
2230                 req, asyncResp->res, "UserName", newUserName, "Password",
2231                 password, "RoleId", roleId, "Enabled", enabled, "Locked",
2232                 locked, "AccountTypes", accountTypes))
2233         {
2234             return;
2235         }
2236     }
2237     else
2238     {
2239         // ConfigureSelf accounts can only modify their own account
2240         if (!userSelf)
2241         {
2242             messages::insufficientPrivilege(asyncResp->res);
2243             return;
2244         }
2245 
2246         // ConfigureSelf accounts can only modify their password
2247         if (!json_util::readJsonPatch(req, asyncResp->res, "Password",
2248                                       password))
2249         {
2250             return;
2251         }
2252     }
2253 
2254     // if user name is not provided in the patch method or if it
2255     // matches the user name in the URI, then we are treating it as
2256     // updating user properties other then username. If username
2257     // provided doesn't match the URI, then we are treating this as
2258     // user rename request.
2259     if (!newUserName || (newUserName.value() == username))
2260     {
2261         updateUserProperties(asyncResp, username, password, enabled, roleId,
2262                              locked, accountTypes, userSelf);
2263         return;
2264     }
2265     crow::connections::systemBus->async_method_call(
2266         [asyncResp, username, password(std::move(password)),
2267          roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)},
2268          locked, userSelf, accountTypes(std::move(accountTypes))](
2269             const boost::system::error_code ec, sdbusplus::message_t& m) {
2270         if (ec)
2271         {
2272             userErrorMessageHandler(m.get_error(), asyncResp, newUser,
2273                                     username);
2274             return;
2275         }
2276 
2277         updateUserProperties(asyncResp, newUser, password, enabled, roleId,
2278                              locked, accountTypes, userSelf);
2279         },
2280         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
2281         "xyz.openbmc_project.User.Manager", "RenameUser", username,
2282         *newUserName);
2283 }
2284 
2285 inline void requestAccountServiceRoutes(App& app)
2286 {
2287     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2288         .privileges(redfish::privileges::headAccountService)
2289         .methods(boost::beast::http::verb::head)(
2290             std::bind_front(handleAccountServiceHead, std::ref(app)));
2291 
2292     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2293         .privileges(redfish::privileges::getAccountService)
2294         .methods(boost::beast::http::verb::get)(
2295             std::bind_front(handleAccountServiceGet, std::ref(app)));
2296 
2297     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2298         .privileges(redfish::privileges::patchAccountService)
2299         .methods(boost::beast::http::verb::patch)(
2300             std::bind_front(handleAccountServicePatch, std::ref(app)));
2301 
2302     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2303         .privileges(redfish::privileges::headManagerAccountCollection)
2304         .methods(boost::beast::http::verb::head)(
2305             std::bind_front(handleAccountCollectionHead, std::ref(app)));
2306 
2307     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2308         .privileges(redfish::privileges::getManagerAccountCollection)
2309         .methods(boost::beast::http::verb::get)(
2310             std::bind_front(handleAccountCollectionGet, std::ref(app)));
2311 
2312     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2313         .privileges(redfish::privileges::postManagerAccountCollection)
2314         .methods(boost::beast::http::verb::post)(
2315             std::bind_front(handleAccountCollectionPost, std::ref(app)));
2316 
2317     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2318         .privileges(redfish::privileges::headManagerAccount)
2319         .methods(boost::beast::http::verb::head)(
2320             std::bind_front(handleAccountHead, std::ref(app)));
2321 
2322     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2323         .privileges(redfish::privileges::getManagerAccount)
2324         .methods(boost::beast::http::verb::get)(
2325             std::bind_front(handleAccountGet, std::ref(app)));
2326 
2327     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2328         // TODO this privilege should be using the generated endpoints, but
2329         // because of the special handling of ConfigureSelf, it's not able to
2330         // yet
2331         .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}})
2332         .methods(boost::beast::http::verb::patch)(
2333             std::bind_front(handleAccountPatch, std::ref(app)));
2334 
2335     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2336         .privileges(redfish::privileges::deleteManagerAccount)
2337         .methods(boost::beast::http::verb::delete_)(
2338             std::bind_front(handleAccountDelete, std::ref(app)));
2339 }
2340 
2341 } // namespace redfish
2342