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 #include "node.hpp"
18 
19 #include <dbus_utility.hpp>
20 #include <error_messages.hpp>
21 #include <openbmc_dbus_rest.hpp>
22 #include <utils/json_utils.hpp>
23 #include <variant>
24 
25 namespace redfish
26 {
27 
28 constexpr const char* ldapConfigObject =
29     "/xyz/openbmc_project/user/ldap/openldap";
30 constexpr const char* ADConfigObject =
31     "/xyz/openbmc_project/user/ldap/active_directory";
32 
33 constexpr const char* ldapRootObject = "/xyz/openbmc_project/user/ldap";
34 constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config";
35 constexpr const char* ldapConfigInterface =
36     "xyz.openbmc_project.User.Ldap.Config";
37 constexpr const char* ldapCreateInterface =
38     "xyz.openbmc_project.User.Ldap.Create";
39 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable";
40 constexpr const char* ldapPrivMapperInterface =
41     "xyz.openbmc_project.User.PrivilegeMapper";
42 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
43 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties";
44 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper";
45 constexpr const char* mapperObjectPath = "/xyz/openbmc_project/object_mapper";
46 constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper";
47 
48 struct LDAPRoleMapData
49 {
50     std::string groupName;
51     std::string privilege;
52 };
53 
54 struct LDAPConfigData
55 {
56     std::string uri{};
57     std::string bindDN{};
58     std::string baseDN{};
59     std::string searchScope{};
60     std::string serverType{};
61     bool serviceEnabled = false;
62     std::string userNameAttribute{};
63     std::string groupAttribute{};
64     std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList;
65 };
66 
67 using ManagedObjectType = std::vector<std::pair<
68     sdbusplus::message::object_path,
69     boost::container::flat_map<
70         std::string, boost::container::flat_map<
71                          std::string, std::variant<bool, std::string>>>>>;
72 using GetObjectType =
73     std::vector<std::pair<std::string, std::vector<std::string>>>;
74 
75 inline std::string getRoleIdFromPrivilege(std::string_view role)
76 {
77     if (role == "priv-admin")
78     {
79         return "Administrator";
80     }
81     else if (role == "priv-callback")
82     {
83         return "Callback";
84     }
85     else if (role == "priv-user")
86     {
87         return "User";
88     }
89     else if (role == "priv-operator")
90     {
91         return "Operator";
92     }
93     return "";
94 }
95 inline std::string getPrivilegeFromRoleId(std::string_view role)
96 {
97     if (role == "Administrator")
98     {
99         return "priv-admin";
100     }
101     else if (role == "Callback")
102     {
103         return "priv-callback";
104     }
105     else if (role == "User")
106     {
107         return "priv-user";
108     }
109     else if (role == "Operator")
110     {
111         return "priv-operator";
112     }
113     return "";
114 }
115 
116 void parseLDAPConfigData(nlohmann::json& json_response,
117                          const LDAPConfigData& confData,
118                          const std::string& ldapType)
119 {
120     std::string service =
121         (ldapType == "LDAP") ? "LDAPService" : "ActiveDirectoryService";
122     nlohmann::json ldap = {
123         {"AccountProviderType", service},
124         {"ServiceEnabled", confData.serviceEnabled},
125         {"ServiceAddresses", nlohmann::json::array({confData.uri})},
126         {"Authentication",
127          {{"AuthenticationType", "UsernameAndPassword"},
128           {"Username", confData.bindDN},
129           {"Password", nullptr}}},
130         {"LDAPService",
131          {{"SearchSettings",
132            {{"BaseDistinguishedNames",
133              nlohmann::json::array({confData.baseDN})},
134             {"UsernameAttribute", confData.userNameAttribute},
135             {"GroupsAttribute", confData.groupAttribute}}}}},
136     };
137 
138     json_response[ldapType].update(std::move(ldap));
139 
140     nlohmann::json& roleMapArray = json_response[ldapType]["RemoteRoleMapping"];
141     roleMapArray = nlohmann::json::array();
142     for (auto& obj : confData.groupRoleList)
143     {
144         BMCWEB_LOG_DEBUG << "Pushing the data groupName="
145                          << obj.second.groupName << "\n";
146         roleMapArray.push_back(
147             {nlohmann::json::array({"RemoteGroup", obj.second.groupName}),
148              nlohmann::json::array(
149                  {"LocalRole", getRoleIdFromPrivilege(obj.second.privilege)})});
150     }
151 }
152 
153 /**
154  *  @brief validates given JSON input and then calls appropriate method to
155  * create, to delete or to set Rolemapping object based on the given input.
156  *
157  */
158 static void handleRoleMapPatch(
159     const std::shared_ptr<AsyncResp>& asyncResp,
160     const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData,
161     const std::string& serverType, std::vector<nlohmann::json>& input)
162 {
163     for (size_t index = 0; index < input.size(); index++)
164     {
165         nlohmann::json& thisJson = input[index];
166 
167         if (thisJson.is_null())
168         {
169             // delete the existing object
170             if (index < roleMapObjData.size())
171             {
172                 crow::connections::systemBus->async_method_call(
173                     [asyncResp, roleMapObjData, serverType,
174                      index](const boost::system::error_code ec) {
175                         if (ec)
176                         {
177                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
178                             messages::internalError(asyncResp->res);
179                             return;
180                         }
181                         asyncResp->res
182                             .jsonValue[serverType]["RemoteRoleMapping"][index] =
183                             nullptr;
184                     },
185                     ldapDbusService, roleMapObjData[index].first,
186                     "xyz.openbmc_project.Object.Delete", "Delete");
187             }
188             else
189             {
190                 BMCWEB_LOG_ERROR << "Can't delete the object";
191                 messages::propertyValueTypeError(
192                     asyncResp->res, thisJson.dump(),
193                     "RemoteRoleMapping/" + std::to_string(index));
194                 return;
195             }
196         }
197         else if (thisJson.empty())
198         {
199             // Don't do anything for the empty objects,parse next json
200             // eg {"RemoteRoleMapping",[{}]}
201         }
202         else
203         {
204             // update/create the object
205             std::optional<std::string> remoteGroup;
206             std::optional<std::string> localRole;
207 
208             if (!json_util::readJson(thisJson, asyncResp->res, "RemoteGroup",
209                                      remoteGroup, "LocalRole", localRole))
210             {
211                 continue;
212             }
213 
214             // Update existing RoleMapping Object
215             if (index < roleMapObjData.size())
216             {
217                 BMCWEB_LOG_DEBUG << "Update Role Map Object";
218                 // If "RemoteGroup" info is provided
219                 if (remoteGroup)
220                 {
221                     crow::connections::systemBus->async_method_call(
222                         [asyncResp, roleMapObjData, serverType, index,
223                          remoteGroup](const boost::system::error_code ec) {
224                             if (ec)
225                             {
226                                 BMCWEB_LOG_ERROR << "DBUS response error: "
227                                                  << ec;
228                                 messages::internalError(asyncResp->res);
229                                 return;
230                             }
231                             asyncResp->res
232                                 .jsonValue[serverType]["RemoteRoleMapping"]
233                                           [index]["RemoteGroup"] = *remoteGroup;
234                         },
235                         ldapDbusService, roleMapObjData[index].first,
236                         propertyInterface, "Set",
237                         "xyz.openbmc_project.User.PrivilegeMapperEntry",
238                         "GroupName",
239                         std::variant<std::string>(std::move(*remoteGroup)));
240                 }
241 
242                 // If "LocalRole" info is provided
243                 if (localRole)
244                 {
245                     crow::connections::systemBus->async_method_call(
246                         [asyncResp, roleMapObjData, serverType, index,
247                          localRole](const boost::system::error_code ec) {
248                             if (ec)
249                             {
250                                 BMCWEB_LOG_ERROR << "DBUS response error: "
251                                                  << ec;
252                                 messages::internalError(asyncResp->res);
253                                 return;
254                             }
255                             asyncResp->res
256                                 .jsonValue[serverType]["RemoteRoleMapping"]
257                                           [index]["LocalRole"] = *localRole;
258                         },
259                         ldapDbusService, roleMapObjData[index].first,
260                         propertyInterface, "Set",
261                         "xyz.openbmc_project.User.PrivilegeMapperEntry",
262                         "Privilege",
263                         std::variant<std::string>(
264                             getPrivilegeFromRoleId(std::move(*localRole))));
265                 }
266             }
267             // Create a new RoleMapping Object.
268             else
269             {
270                 BMCWEB_LOG_DEBUG
271                     << "setRoleMappingProperties: Creating new Object";
272                 std::string pathString =
273                     "RemoteRoleMapping/" + std::to_string(index);
274 
275                 if (!localRole)
276                 {
277                     messages::propertyMissing(asyncResp->res,
278                                               pathString + "/LocalRole");
279                     continue;
280                 }
281                 if (!remoteGroup)
282                 {
283                     messages::propertyMissing(asyncResp->res,
284                                               pathString + "/RemoteGroup");
285                     continue;
286                 }
287 
288                 std::string dbusObjectPath;
289                 if (serverType == "ActiveDirectory")
290                 {
291                     dbusObjectPath = ADConfigObject;
292                 }
293                 else if (serverType == "LDAP")
294                 {
295                     dbusObjectPath = ldapConfigObject;
296                 }
297 
298                 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup
299                                  << ",LocalRole=" << *localRole;
300 
301                 crow::connections::systemBus->async_method_call(
302                     [asyncResp, serverType, localRole,
303                      remoteGroup](const boost::system::error_code ec) {
304                         if (ec)
305                         {
306                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
307                             messages::internalError(asyncResp->res);
308                             return;
309                         }
310                         nlohmann::json& remoteRoleJson =
311                             asyncResp->res
312                                 .jsonValue[serverType]["RemoteRoleMapping"];
313                         remoteRoleJson.push_back(
314                             {{"LocalRole", *localRole},
315                              {"RemoteGroup", *remoteGroup}});
316                     },
317                     ldapDbusService, dbusObjectPath, ldapPrivMapperInterface,
318                     "Create", std::move(*remoteGroup),
319                     getPrivilegeFromRoleId(std::move(*localRole)));
320             }
321         }
322     }
323 }
324 
325 /**
326  * Function that retrieves all properties for LDAP config object
327  * into JSON
328  */
329 template <typename CallbackFunc>
330 inline void getLDAPConfigData(const std::string& ldapType,
331                               CallbackFunc&& callback)
332 {
333 
334     const std::array<const char*, 2> interfaces = {ldapEnableInterface,
335                                                    ldapConfigInterface};
336 
337     crow::connections::systemBus->async_method_call(
338         [callback, ldapType](const boost::system::error_code ec,
339                              const GetObjectType& resp) {
340             LDAPConfigData confData{};
341             if (ec || resp.empty())
342             {
343                 BMCWEB_LOG_ERROR << "DBUS response error during getting of "
344                                     "service name: "
345                                  << ec;
346                 callback(false, confData, ldapType);
347                 return;
348             }
349             std::string service = resp.begin()->first;
350             crow::connections::systemBus->async_method_call(
351                 [callback, ldapType](const boost::system::error_code error_code,
352                                      const ManagedObjectType& ldapObjects) {
353                     LDAPConfigData confData{};
354                     if (error_code)
355                     {
356                         callback(false, confData, ldapType);
357                         BMCWEB_LOG_ERROR << "D-Bus responses error: "
358                                          << error_code;
359                         return;
360                     }
361 
362                     std::string ldapDbusType;
363                     std::string searchString;
364 
365                     if (ldapType == "LDAP")
366                     {
367                         ldapDbusType = "xyz.openbmc_project.User.Ldap.Config."
368                                        "Type.OpenLdap";
369                         searchString = "openldap";
370                     }
371                     else if (ldapType == "ActiveDirectory")
372                     {
373                         ldapDbusType =
374                             "xyz.openbmc_project.User.Ldap.Config.Type."
375                             "ActiveDirectory";
376                         searchString = "active_directory";
377                     }
378                     else
379                     {
380                         BMCWEB_LOG_ERROR
381                             << "Can't get the DbusType for the given type="
382                             << ldapType;
383                         callback(false, confData, ldapType);
384                         return;
385                     }
386 
387                     std::string ldapEnableInterfaceStr = ldapEnableInterface;
388                     std::string ldapConfigInterfaceStr = ldapConfigInterface;
389 
390                     for (const auto& object : ldapObjects)
391                     {
392                         // let's find the object whose ldap type is equal to the
393                         // given type
394                         if (object.first.str.find(searchString) ==
395                             std::string::npos)
396                         {
397                             continue;
398                         }
399 
400                         for (const auto& interface : object.second)
401                         {
402                             if (interface.first == ldapEnableInterfaceStr)
403                             {
404                                 // rest of the properties are string.
405                                 for (const auto& property : interface.second)
406                                 {
407                                     if (property.first == "Enabled")
408                                     {
409                                         const bool* value =
410                                             std::get_if<bool>(&property.second);
411                                         if (value == nullptr)
412                                         {
413                                             continue;
414                                         }
415                                         confData.serviceEnabled = *value;
416                                         break;
417                                     }
418                                 }
419                             }
420                             else if (interface.first == ldapConfigInterfaceStr)
421                             {
422 
423                                 for (const auto& property : interface.second)
424                                 {
425                                     const std::string* strValue =
426                                         std::get_if<std::string>(
427                                             &property.second);
428                                     if (strValue == nullptr)
429                                     {
430                                         continue;
431                                     }
432                                     if (property.first == "LDAPServerURI")
433                                     {
434                                         confData.uri = *strValue;
435                                     }
436                                     else if (property.first == "LDAPBindDN")
437                                     {
438                                         confData.bindDN = *strValue;
439                                     }
440                                     else if (property.first == "LDAPBaseDN")
441                                     {
442                                         confData.baseDN = *strValue;
443                                     }
444                                     else if (property.first ==
445                                              "LDAPSearchScope")
446                                     {
447                                         confData.searchScope = *strValue;
448                                     }
449                                     else if (property.first ==
450                                              "GroupNameAttribute")
451                                     {
452                                         confData.groupAttribute = *strValue;
453                                     }
454                                     else if (property.first ==
455                                              "UserNameAttribute")
456                                     {
457                                         confData.userNameAttribute = *strValue;
458                                     }
459                                     else if (property.first == "LDAPType")
460                                     {
461                                         confData.serverType = *strValue;
462                                     }
463                                 }
464                             }
465                             else if (interface.first ==
466                                      "xyz.openbmc_project.User."
467                                      "PrivilegeMapperEntry")
468                             {
469                                 LDAPRoleMapData roleMapData{};
470                                 for (const auto& property : interface.second)
471                                 {
472                                     const std::string* strValue =
473                                         std::get_if<std::string>(
474                                             &property.second);
475 
476                                     if (strValue == nullptr)
477                                     {
478                                         continue;
479                                     }
480 
481                                     if (property.first == "GroupName")
482                                     {
483                                         roleMapData.groupName = *strValue;
484                                     }
485                                     else if (property.first == "Privilege")
486                                     {
487                                         roleMapData.privilege = *strValue;
488                                     }
489                                 }
490 
491                                 confData.groupRoleList.push_back(std::make_pair(
492                                     object.first.str, roleMapData));
493                             }
494                         }
495                     }
496                     callback(true, confData, ldapType);
497                 },
498                 service, ldapRootObject, dbusObjManagerIntf,
499                 "GetManagedObjects");
500         },
501         mapperBusName, mapperObjectPath, mapperIntf, "GetObject",
502         ldapConfigObject, interfaces);
503 }
504 
505 class AccountService : public Node
506 {
507   public:
508     AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/")
509     {
510         entityPrivileges = {
511             {boost::beast::http::verb::get,
512              {{"ConfigureUsers"}, {"ConfigureManager"}}},
513             {boost::beast::http::verb::head, {{"Login"}}},
514             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
515             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
516             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
517             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
518     }
519 
520   private:
521     /**
522      * @brief parses the authentication section under the LDAP
523      * @param input JSON data
524      * @param asyncResp pointer to the JSON response
525      * @param userName  userName to be filled from the given JSON.
526      * @param password  password to be filled from the given JSON.
527      */
528     void
529         parseLDAPAuthenticationJson(nlohmann::json input,
530                                     const std::shared_ptr<AsyncResp>& asyncResp,
531                                     std::optional<std::string>& username,
532                                     std::optional<std::string>& password)
533     {
534         std::optional<std::string> authType;
535 
536         if (!json_util::readJson(input, asyncResp->res, "AuthenticationType",
537                                  authType, "Username", username, "Password",
538                                  password))
539         {
540             return;
541         }
542         if (!authType)
543         {
544             return;
545         }
546         if (*authType != "UsernameAndPassword")
547         {
548             messages::propertyValueNotInList(asyncResp->res, *authType,
549                                              "AuthenticationType");
550             return;
551         }
552     }
553     /**
554      * @brief parses the LDAPService section under the LDAP
555      * @param input JSON data
556      * @param asyncResp pointer to the JSON response
557      * @param baseDNList baseDN to be filled from the given JSON.
558      * @param userNameAttribute  userName to be filled from the given JSON.
559      * @param groupaAttribute  password to be filled from the given JSON.
560      */
561 
562     void parseLDAPServiceJson(
563         nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp,
564         std::optional<std::vector<std::string>>& baseDNList,
565         std::optional<std::string>& userNameAttribute,
566         std::optional<std::string>& groupsAttribute)
567     {
568         std::optional<nlohmann::json> searchSettings;
569 
570         if (!json_util::readJson(input, asyncResp->res, "SearchSettings",
571                                  searchSettings))
572         {
573             return;
574         }
575         if (!searchSettings)
576         {
577             return;
578         }
579         if (!json_util::readJson(*searchSettings, asyncResp->res,
580                                  "BaseDistinguishedNames", baseDNList,
581                                  "UsernameAttribute", userNameAttribute,
582                                  "GroupsAttribute", groupsAttribute))
583         {
584             return;
585         }
586     }
587     /**
588      * @brief updates the LDAP server address and updates the
589               json response with the new value.
590      * @param serviceAddressList address to be updated.
591      * @param asyncResp pointer to the JSON response
592      * @param ldapServerElementName Type of LDAP
593      server(openLDAP/ActiveDirectory)
594      */
595 
596     void handleServiceAddressPatch(
597         const std::vector<std::string>& serviceAddressList,
598         const std::shared_ptr<AsyncResp>& asyncResp,
599         const std::string& ldapServerElementName,
600         const std::string& ldapConfigObject)
601     {
602         crow::connections::systemBus->async_method_call(
603             [asyncResp, ldapServerElementName,
604              serviceAddressList](const boost::system::error_code ec) {
605                 if (ec)
606                 {
607                     BMCWEB_LOG_DEBUG
608                         << "Error Occured in updating the service address";
609                     messages::internalError(asyncResp->res);
610                     return;
611                 }
612                 std::vector<std::string> modifiedserviceAddressList = {
613                     serviceAddressList.front()};
614                 asyncResp->res
615                     .jsonValue[ldapServerElementName]["ServiceAddresses"] =
616                     modifiedserviceAddressList;
617                 if ((serviceAddressList).size() > 1)
618                 {
619                     messages::propertyValueModified(asyncResp->res,
620                                                     "ServiceAddresses",
621                                                     serviceAddressList.front());
622                 }
623                 BMCWEB_LOG_DEBUG << "Updated the service address";
624             },
625             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
626             ldapConfigInterface, "LDAPServerURI",
627             std::variant<std::string>(serviceAddressList.front()));
628     }
629     /**
630      * @brief updates the LDAP Bind DN and updates the
631               json response with the new value.
632      * @param username name of the user which needs to be updated.
633      * @param asyncResp pointer to the JSON response
634      * @param ldapServerElementName Type of LDAP
635      server(openLDAP/ActiveDirectory)
636      */
637 
638     void handleUserNamePatch(const std::string& username,
639                              const std::shared_ptr<AsyncResp>& asyncResp,
640                              const std::string& ldapServerElementName,
641                              const std::string& ldapConfigObject)
642     {
643         crow::connections::systemBus->async_method_call(
644             [asyncResp, username,
645              ldapServerElementName](const boost::system::error_code ec) {
646                 if (ec)
647                 {
648                     BMCWEB_LOG_DEBUG
649                         << "Error occured in updating the username";
650                     messages::internalError(asyncResp->res);
651                     return;
652                 }
653                 asyncResp->res.jsonValue[ldapServerElementName]
654                                         ["Authentication"]["Username"] =
655                     username;
656                 BMCWEB_LOG_DEBUG << "Updated the username";
657             },
658             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
659             ldapConfigInterface, "LDAPBindDN",
660             std::variant<std::string>(username));
661     }
662 
663     /**
664      * @brief updates the LDAP password
665      * @param password : ldap password which needs to be updated.
666      * @param asyncResp pointer to the JSON response
667      * @param ldapServerElementName Type of LDAP
668      *        server(openLDAP/ActiveDirectory)
669      */
670 
671     void handlePasswordPatch(const std::string& password,
672                              const std::shared_ptr<AsyncResp>& asyncResp,
673                              const std::string& ldapServerElementName,
674                              const std::string& ldapConfigObject)
675     {
676         crow::connections::systemBus->async_method_call(
677             [asyncResp, password,
678              ldapServerElementName](const boost::system::error_code ec) {
679                 if (ec)
680                 {
681                     BMCWEB_LOG_DEBUG
682                         << "Error occured in updating the password";
683                     messages::internalError(asyncResp->res);
684                     return;
685                 }
686                 asyncResp->res.jsonValue[ldapServerElementName]
687                                         ["Authentication"]["Password"] = "";
688                 BMCWEB_LOG_DEBUG << "Updated the password";
689             },
690             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
691             ldapConfigInterface, "LDAPBindDNPassword",
692             std::variant<std::string>(password));
693     }
694 
695     /**
696      * @brief updates the LDAP BaseDN and updates the
697               json response with the new value.
698      * @param baseDNList baseDN list which needs to be updated.
699      * @param asyncResp pointer to the JSON response
700      * @param ldapServerElementName Type of LDAP
701      server(openLDAP/ActiveDirectory)
702      */
703 
704     void handleBaseDNPatch(const std::vector<std::string>& baseDNList,
705                            const std::shared_ptr<AsyncResp>& asyncResp,
706                            const std::string& ldapServerElementName,
707                            const std::string& ldapConfigObject)
708     {
709         crow::connections::systemBus->async_method_call(
710             [asyncResp, baseDNList,
711              ldapServerElementName](const boost::system::error_code ec) {
712                 if (ec)
713                 {
714                     BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN";
715                     messages::internalError(asyncResp->res);
716                     return;
717                 }
718                 auto& serverTypeJson =
719                     asyncResp->res.jsonValue[ldapServerElementName];
720                 auto& searchSettingsJson =
721                     serverTypeJson["LDAPService"]["SearchSettings"];
722                 std::vector<std::string> modifiedBaseDNList = {
723                     baseDNList.front()};
724                 searchSettingsJson["BaseDistinguishedNames"] =
725                     modifiedBaseDNList;
726                 if (baseDNList.size() > 1)
727                 {
728                     messages::propertyValueModified(asyncResp->res,
729                                                     "BaseDistinguishedNames",
730                                                     baseDNList.front());
731                 }
732                 BMCWEB_LOG_DEBUG << "Updated the base DN";
733             },
734             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
735             ldapConfigInterface, "LDAPBaseDN",
736             std::variant<std::string>(baseDNList.front()));
737     }
738     /**
739      * @brief updates the LDAP user name attribute and updates the
740               json response with the new value.
741      * @param userNameAttribute attribute to be updated.
742      * @param asyncResp pointer to the JSON response
743      * @param ldapServerElementName Type of LDAP
744      server(openLDAP/ActiveDirectory)
745      */
746 
747     void handleUserNameAttrPatch(const std::string& userNameAttribute,
748                                  const std::shared_ptr<AsyncResp>& asyncResp,
749                                  const std::string& ldapServerElementName,
750                                  const std::string& ldapConfigObject)
751     {
752         crow::connections::systemBus->async_method_call(
753             [asyncResp, userNameAttribute,
754              ldapServerElementName](const boost::system::error_code ec) {
755                 if (ec)
756                 {
757                     BMCWEB_LOG_DEBUG << "Error Occured in Updating the "
758                                         "username attribute";
759                     messages::internalError(asyncResp->res);
760                     return;
761                 }
762                 auto& serverTypeJson =
763                     asyncResp->res.jsonValue[ldapServerElementName];
764                 auto& searchSettingsJson =
765                     serverTypeJson["LDAPService"]["SearchSettings"];
766                 searchSettingsJson["UsernameAttribute"] = userNameAttribute;
767                 BMCWEB_LOG_DEBUG << "Updated the user name attr.";
768             },
769             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
770             ldapConfigInterface, "UserNameAttribute",
771             std::variant<std::string>(userNameAttribute));
772     }
773     /**
774      * @brief updates the LDAP group attribute and updates the
775               json response with the new value.
776      * @param groupsAttribute attribute to be updated.
777      * @param asyncResp pointer to the JSON response
778      * @param ldapServerElementName Type of LDAP
779      server(openLDAP/ActiveDirectory)
780      */
781 
782     void handleGroupNameAttrPatch(const std::string& groupsAttribute,
783                                   const std::shared_ptr<AsyncResp>& asyncResp,
784                                   const std::string& ldapServerElementName,
785                                   const std::string& ldapConfigObject)
786     {
787         crow::connections::systemBus->async_method_call(
788             [asyncResp, groupsAttribute,
789              ldapServerElementName](const boost::system::error_code ec) {
790                 if (ec)
791                 {
792                     BMCWEB_LOG_DEBUG << "Error Occured in Updating the "
793                                         "groupname attribute";
794                     messages::internalError(asyncResp->res);
795                     return;
796                 }
797                 auto& serverTypeJson =
798                     asyncResp->res.jsonValue[ldapServerElementName];
799                 auto& searchSettingsJson =
800                     serverTypeJson["LDAPService"]["SearchSettings"];
801                 searchSettingsJson["GroupsAttribute"] = groupsAttribute;
802                 BMCWEB_LOG_DEBUG << "Updated the groupname attr";
803             },
804             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
805             ldapConfigInterface, "GroupNameAttribute",
806             std::variant<std::string>(groupsAttribute));
807     }
808     /**
809      * @brief updates the LDAP service enable and updates the
810               json response with the new value.
811      * @param input JSON data.
812      * @param asyncResp pointer to the JSON response
813      * @param ldapServerElementName Type of LDAP
814      server(openLDAP/ActiveDirectory)
815      */
816 
817     void handleServiceEnablePatch(bool serviceEnabled,
818                                   const std::shared_ptr<AsyncResp>& asyncResp,
819                                   const std::string& ldapServerElementName,
820                                   const std::string& ldapConfigObject)
821     {
822         crow::connections::systemBus->async_method_call(
823             [asyncResp, serviceEnabled,
824              ldapServerElementName](const boost::system::error_code ec) {
825                 if (ec)
826                 {
827                     BMCWEB_LOG_DEBUG
828                         << "Error Occured in Updating the service enable";
829                     messages::internalError(asyncResp->res);
830                     return;
831                 }
832                 asyncResp->res
833                     .jsonValue[ldapServerElementName]["ServiceEnabled"] =
834                     serviceEnabled;
835                 BMCWEB_LOG_DEBUG << "Updated Service enable = "
836                                  << serviceEnabled;
837             },
838             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
839             ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled));
840     }
841 
842     /**
843      * @brief Get the required values from the given JSON, validates the
844      *        value and create the LDAP config object.
845      * @param input JSON data
846      * @param asyncResp pointer to the JSON response
847      * @param serverType Type of LDAP server(openLDAP/ActiveDirectory)
848      */
849 
850     void handleLDAPPatch(nlohmann::json& input,
851                          const std::shared_ptr<AsyncResp>& asyncResp,
852                          const crow::Request& req,
853                          const std::vector<std::string>& params,
854                          const std::string& serverType)
855     {
856         std::string dbusObjectPath;
857         if (serverType == "ActiveDirectory")
858         {
859             dbusObjectPath = ADConfigObject;
860         }
861         else if (serverType == "LDAP")
862         {
863             dbusObjectPath = ldapConfigObject;
864         }
865 
866         std::optional<nlohmann::json> authentication;
867         std::optional<nlohmann::json> ldapService;
868         std::optional<std::string> accountProviderType;
869         std::optional<std::vector<std::string>> serviceAddressList;
870         std::optional<bool> serviceEnabled;
871         std::optional<std::vector<std::string>> baseDNList;
872         std::optional<std::string> userNameAttribute;
873         std::optional<std::string> groupsAttribute;
874         std::optional<std::string> userName;
875         std::optional<std::string> password;
876         std::optional<std::vector<nlohmann::json>> remoteRoleMapData;
877 
878         if (!json_util::readJson(input, asyncResp->res, "Authentication",
879                                  authentication, "LDAPService", ldapService,
880                                  "ServiceAddresses", serviceAddressList,
881                                  "AccountProviderType", accountProviderType,
882                                  "ServiceEnabled", serviceEnabled,
883                                  "RemoteRoleMapping", remoteRoleMapData))
884         {
885             return;
886         }
887 
888         if (authentication)
889         {
890             parseLDAPAuthenticationJson(*authentication, asyncResp, userName,
891                                         password);
892         }
893         if (ldapService)
894         {
895             parseLDAPServiceJson(*ldapService, asyncResp, baseDNList,
896                                  userNameAttribute, groupsAttribute);
897         }
898         if (accountProviderType)
899         {
900             messages::propertyNotWritable(asyncResp->res,
901                                           "AccountProviderType");
902         }
903         if (serviceAddressList)
904         {
905             if ((*serviceAddressList).size() == 0)
906             {
907                 messages::propertyValueNotInList(asyncResp->res, "[]",
908                                                  "ServiceAddress");
909                 return;
910             }
911         }
912         if (baseDNList)
913         {
914             if ((*baseDNList).size() == 0)
915             {
916                 messages::propertyValueNotInList(asyncResp->res, "[]",
917                                                  "BaseDistinguishedNames");
918                 return;
919             }
920         }
921 
922         // nothing to update, then return
923         if (!userName && !password && !serviceAddressList && !baseDNList &&
924             !userNameAttribute && !groupsAttribute && !serviceEnabled &&
925             !remoteRoleMapData)
926         {
927             return;
928         }
929 
930         // Get the existing resource first then keep modifying
931         // whenever any property gets updated.
932         getLDAPConfigData(serverType, [this, asyncResp, userName, password,
933                                        baseDNList, userNameAttribute,
934                                        groupsAttribute, accountProviderType,
935                                        serviceAddressList, serviceEnabled,
936                                        dbusObjectPath, remoteRoleMapData](
937                                           bool success, LDAPConfigData confData,
938                                           const std::string& serverType) {
939             if (!success)
940             {
941                 messages::internalError(asyncResp->res);
942                 return;
943             }
944             parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType);
945             if (confData.serviceEnabled)
946             {
947                 // Disable the service first and update the rest of
948                 // the properties.
949                 handleServiceEnablePatch(false, asyncResp, serverType,
950                                          dbusObjectPath);
951             }
952 
953             if (serviceAddressList)
954             {
955                 handleServiceAddressPatch(*serviceAddressList, asyncResp,
956                                           serverType, dbusObjectPath);
957             }
958             if (userName)
959             {
960                 handleUserNamePatch(*userName, asyncResp, serverType,
961                                     dbusObjectPath);
962             }
963             if (password)
964             {
965                 handlePasswordPatch(*password, asyncResp, serverType,
966                                     dbusObjectPath);
967             }
968 
969             if (baseDNList)
970             {
971                 handleBaseDNPatch(*baseDNList, asyncResp, serverType,
972                                   dbusObjectPath);
973             }
974             if (userNameAttribute)
975             {
976                 handleUserNameAttrPatch(*userNameAttribute, asyncResp,
977                                         serverType, dbusObjectPath);
978             }
979             if (groupsAttribute)
980             {
981                 handleGroupNameAttrPatch(*groupsAttribute, asyncResp,
982                                          serverType, dbusObjectPath);
983             }
984             if (serviceEnabled)
985             {
986                 // if user has given the value as true then enable
987                 // the service. if user has given false then no-op
988                 // as service is already stopped.
989                 if (*serviceEnabled)
990                 {
991                     handleServiceEnablePatch(*serviceEnabled, asyncResp,
992                                              serverType, dbusObjectPath);
993                 }
994             }
995             else
996             {
997                 // if user has not given the service enabled value
998                 // then revert it to the same state as it was
999                 // before.
1000                 handleServiceEnablePatch(confData.serviceEnabled, asyncResp,
1001                                          serverType, dbusObjectPath);
1002             }
1003 
1004             if (remoteRoleMapData)
1005             {
1006                 std::vector<nlohmann::json> remoteRoleMap =
1007                     std::move(*remoteRoleMapData);
1008 
1009                 handleRoleMapPatch(asyncResp, confData.groupRoleList,
1010                                    serverType, remoteRoleMap);
1011             }
1012         });
1013     }
1014 
1015     void doGet(crow::Response& res, const crow::Request& req,
1016                const std::vector<std::string>& params) override
1017     {
1018         auto asyncResp = std::make_shared<AsyncResp>(res);
1019         res.jsonValue = {
1020             {"@odata.context", "/redfish/v1/"
1021                                "$metadata#AccountService.AccountService"},
1022             {"@odata.id", "/redfish/v1/AccountService"},
1023             {"@odata.type", "#AccountService."
1024                             "v1_4_0.AccountService"},
1025             {"Id", "AccountService"},
1026             {"Name", "Account Service"},
1027             {"Description", "Account Service"},
1028             {"ServiceEnabled", true},
1029             {"MaxPasswordLength", 20},
1030             {"Accounts",
1031              {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}},
1032             {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}},
1033             {"LDAP",
1034              {{"Certificates",
1035                {{"@odata.id",
1036                  "/redfish/v1/AccountService/LDAP/Certificates"}}}}}};
1037         crow::connections::systemBus->async_method_call(
1038             [asyncResp](
1039                 const boost::system::error_code ec,
1040                 const std::vector<std::pair<
1041                     std::string, std::variant<uint32_t, uint16_t, uint8_t>>>&
1042                     propertiesList) {
1043                 if (ec)
1044                 {
1045                     messages::internalError(asyncResp->res);
1046                     return;
1047                 }
1048                 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
1049                                  << "properties for AccountService";
1050                 for (const std::pair<std::string,
1051                                      std::variant<uint32_t, uint16_t, uint8_t>>&
1052                          property : propertiesList)
1053                 {
1054                     if (property.first == "MinPasswordLength")
1055                     {
1056                         const uint8_t* value =
1057                             std::get_if<uint8_t>(&property.second);
1058                         if (value != nullptr)
1059                         {
1060                             asyncResp->res.jsonValue["MinPasswordLength"] =
1061                                 *value;
1062                         }
1063                     }
1064                     if (property.first == "AccountUnlockTimeout")
1065                     {
1066                         const uint32_t* value =
1067                             std::get_if<uint32_t>(&property.second);
1068                         if (value != nullptr)
1069                         {
1070                             asyncResp->res.jsonValue["AccountLockoutDuration"] =
1071                                 *value;
1072                         }
1073                     }
1074                     if (property.first == "MaxLoginAttemptBeforeLockout")
1075                     {
1076                         const uint16_t* value =
1077                             std::get_if<uint16_t>(&property.second);
1078                         if (value != nullptr)
1079                         {
1080                             asyncResp->res
1081                                 .jsonValue["AccountLockoutThreshold"] = *value;
1082                         }
1083                     }
1084                 }
1085             },
1086             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1087             "org.freedesktop.DBus.Properties", "GetAll",
1088             "xyz.openbmc_project.User.AccountPolicy");
1089 
1090         auto callback = [asyncResp](bool success, LDAPConfigData& confData,
1091                                     const std::string& ldapType) {
1092             parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1093         };
1094 
1095         getLDAPConfigData("LDAP", callback);
1096         getLDAPConfigData("ActiveDirectory", callback);
1097     }
1098 
1099     void doPatch(crow::Response& res, const crow::Request& req,
1100                  const std::vector<std::string>& params) override
1101     {
1102         auto asyncResp = std::make_shared<AsyncResp>(res);
1103 
1104         std::optional<uint32_t> unlockTimeout;
1105         std::optional<uint16_t> lockoutThreshold;
1106         std::optional<uint16_t> minPasswordLength;
1107         std::optional<uint16_t> maxPasswordLength;
1108         std::optional<nlohmann::json> ldapObject;
1109         std::optional<nlohmann::json> activeDirectoryObject;
1110 
1111         if (!json_util::readJson(req, res, "AccountLockoutDuration",
1112                                  unlockTimeout, "AccountLockoutThreshold",
1113                                  lockoutThreshold, "MaxPasswordLength",
1114                                  maxPasswordLength, "MinPasswordLength",
1115                                  minPasswordLength, "LDAP", ldapObject,
1116                                  "ActiveDirectory", activeDirectoryObject))
1117         {
1118             return;
1119         }
1120 
1121         if (minPasswordLength)
1122         {
1123             messages::propertyNotWritable(asyncResp->res, "MinPasswordLength");
1124         }
1125 
1126         if (maxPasswordLength)
1127         {
1128             messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1129         }
1130 
1131         if (ldapObject)
1132         {
1133             handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP");
1134         }
1135 
1136         if (activeDirectoryObject)
1137         {
1138             handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params,
1139                             "ActiveDirectory");
1140         }
1141 
1142         if (unlockTimeout)
1143         {
1144             crow::connections::systemBus->async_method_call(
1145                 [asyncResp](const boost::system::error_code ec) {
1146                     if (ec)
1147                     {
1148                         messages::internalError(asyncResp->res);
1149                         return;
1150                     }
1151                     messages::success(asyncResp->res);
1152                 },
1153                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1154                 "org.freedesktop.DBus.Properties", "Set",
1155                 "xyz.openbmc_project.User.AccountPolicy",
1156                 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout));
1157         }
1158         if (lockoutThreshold)
1159         {
1160             crow::connections::systemBus->async_method_call(
1161                 [asyncResp](const boost::system::error_code ec) {
1162                     if (ec)
1163                     {
1164                         messages::internalError(asyncResp->res);
1165                         return;
1166                     }
1167                     messages::success(asyncResp->res);
1168                 },
1169                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1170                 "org.freedesktop.DBus.Properties", "Set",
1171                 "xyz.openbmc_project.User.AccountPolicy",
1172                 "MaxLoginAttemptBeforeLockout",
1173                 std::variant<uint16_t>(*lockoutThreshold));
1174         }
1175     }
1176 };
1177 
1178 class AccountsCollection : public Node
1179 {
1180   public:
1181     AccountsCollection(CrowApp& app) :
1182         Node(app, "/redfish/v1/AccountService/Accounts/")
1183     {
1184         entityPrivileges = {
1185             {boost::beast::http::verb::get,
1186              {{"ConfigureUsers"}, {"ConfigureManager"}}},
1187             {boost::beast::http::verb::head, {{"Login"}}},
1188             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
1189             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1190             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1191             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1192     }
1193 
1194   private:
1195     void doGet(crow::Response& res, const crow::Request& req,
1196                const std::vector<std::string>& params) override
1197     {
1198         auto asyncResp = std::make_shared<AsyncResp>(res);
1199         res.jsonValue = {{"@odata.context",
1200                           "/redfish/v1/"
1201                           "$metadata#ManagerAccountCollection."
1202                           "ManagerAccountCollection"},
1203                          {"@odata.id", "/redfish/v1/AccountService/Accounts"},
1204                          {"@odata.type", "#ManagerAccountCollection."
1205                                          "ManagerAccountCollection"},
1206                          {"Name", "Accounts Collection"},
1207                          {"Description", "BMC User Accounts"}};
1208 
1209         crow::connections::systemBus->async_method_call(
1210             [asyncResp](const boost::system::error_code ec,
1211                         const ManagedObjectType& users) {
1212                 if (ec)
1213                 {
1214                     messages::internalError(asyncResp->res);
1215                     return;
1216                 }
1217 
1218                 nlohmann::json& memberArray =
1219                     asyncResp->res.jsonValue["Members"];
1220                 memberArray = nlohmann::json::array();
1221 
1222                 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
1223                 for (auto& user : users)
1224                 {
1225                     const std::string& path =
1226                         static_cast<const std::string&>(user.first);
1227                     std::size_t lastIndex = path.rfind("/");
1228                     if (lastIndex == std::string::npos)
1229                     {
1230                         lastIndex = 0;
1231                     }
1232                     else
1233                     {
1234                         lastIndex += 1;
1235                     }
1236                     memberArray.push_back(
1237                         {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
1238                                            path.substr(lastIndex)}});
1239                 }
1240             },
1241             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1242             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1243     }
1244     void doPost(crow::Response& res, const crow::Request& req,
1245                 const std::vector<std::string>& params) override
1246     {
1247         auto asyncResp = std::make_shared<AsyncResp>(res);
1248 
1249         std::string username;
1250         std::string password;
1251         std::optional<std::string> roleId("User");
1252         std::optional<bool> enabled = true;
1253         if (!json_util::readJson(req, res, "UserName", username, "Password",
1254                                  password, "RoleId", roleId, "Enabled",
1255                                  enabled))
1256         {
1257             return;
1258         }
1259 
1260         std::string priv = getPrivilegeFromRoleId(*roleId);
1261         if (priv.empty())
1262         {
1263             messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
1264             return;
1265         }
1266         roleId = priv;
1267 
1268         // Reading AllGroups property
1269         crow::connections::systemBus->async_method_call(
1270             [asyncResp, username, password{std::move(password)}, roleId,
1271              enabled](const boost::system::error_code ec,
1272                       const std::variant<std::vector<std::string>>& allGroups) {
1273                 if (ec)
1274                 {
1275                     BMCWEB_LOG_DEBUG << "ERROR with async_method_call";
1276                     messages::internalError(asyncResp->res);
1277                     return;
1278                 }
1279 
1280                 const std::vector<std::string>* allGroupsList =
1281                     std::get_if<std::vector<std::string>>(&allGroups);
1282 
1283                 if (allGroupsList == nullptr || allGroupsList->empty())
1284                 {
1285                     messages::internalError(asyncResp->res);
1286                     return;
1287                 }
1288 
1289                 crow::connections::systemBus->async_method_call(
1290                     [asyncResp, username, password{std::move(password)}](
1291                         const boost::system::error_code ec) {
1292                         if (ec)
1293                         {
1294                             messages::resourceAlreadyExists(
1295                                 asyncResp->res,
1296                                 "#ManagerAccount.v1_0_3.ManagerAccount",
1297                                 "UserName", username);
1298                             return;
1299                         }
1300 
1301                         if (!pamUpdatePassword(username, password))
1302                         {
1303                             // At this point we have a user that's been created,
1304                             // but the password set failed.Something is wrong,
1305                             // so delete the user that we've already created
1306                             crow::connections::systemBus->async_method_call(
1307                                 [asyncResp](
1308                                     const boost::system::error_code ec) {
1309                                     if (ec)
1310                                     {
1311                                         messages::internalError(asyncResp->res);
1312                                         return;
1313                                     }
1314 
1315                                     messages::invalidObject(asyncResp->res,
1316                                                             "Password");
1317                                 },
1318                                 "xyz.openbmc_project.User.Manager",
1319                                 "/xyz/openbmc_project/user/" + username,
1320                                 "xyz.openbmc_project.Object.Delete", "Delete");
1321 
1322                             BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1323                             return;
1324                         }
1325 
1326                         messages::created(asyncResp->res);
1327                         asyncResp->res.addHeader(
1328                             "Location",
1329                             "/redfish/v1/AccountService/Accounts/" + username);
1330                     },
1331                     "xyz.openbmc_project.User.Manager",
1332                     "/xyz/openbmc_project/user",
1333                     "xyz.openbmc_project.User.Manager", "CreateUser", username,
1334                     *allGroupsList, *roleId, *enabled);
1335             },
1336             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1337             "org.freedesktop.DBus.Properties", "Get",
1338             "xyz.openbmc_project.User.Manager", "AllGroups");
1339     }
1340 };
1341 
1342 class ManagerAccount : public Node
1343 {
1344   public:
1345     ManagerAccount(CrowApp& app) :
1346         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
1347     {
1348         entityPrivileges = {
1349             {boost::beast::http::verb::get,
1350              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
1351             {boost::beast::http::verb::head, {{"Login"}}},
1352             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
1353             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1354             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1355             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1356     }
1357 
1358   private:
1359     void doGet(crow::Response& res, const crow::Request& req,
1360                const std::vector<std::string>& params) override
1361     {
1362 
1363         auto asyncResp = std::make_shared<AsyncResp>(res);
1364 
1365         if (params.size() != 1)
1366         {
1367             messages::internalError(asyncResp->res);
1368             return;
1369         }
1370 
1371         crow::connections::systemBus->async_method_call(
1372             [asyncResp, accountName{std::string(params[0])}](
1373                 const boost::system::error_code ec,
1374                 const ManagedObjectType& users) {
1375                 if (ec)
1376                 {
1377                     messages::internalError(asyncResp->res);
1378                     return;
1379                 }
1380                 auto userIt = users.begin();
1381 
1382                 for (; userIt != users.end(); userIt++)
1383                 {
1384                     if (boost::ends_with(userIt->first.str, "/" + accountName))
1385                     {
1386                         break;
1387                     }
1388                 }
1389                 if (userIt == users.end())
1390                 {
1391                     messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1392                                                accountName);
1393                     return;
1394                 }
1395 
1396                 asyncResp->res.jsonValue = {
1397                     {"@odata.context",
1398                      "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
1399                     {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
1400                     {"Name", "User Account"},
1401                     {"Description", "User Account"},
1402                     {"Password", nullptr}};
1403 
1404                 for (const auto& interface : userIt->second)
1405                 {
1406                     if (interface.first ==
1407                         "xyz.openbmc_project.User.Attributes")
1408                     {
1409                         for (const auto& property : interface.second)
1410                         {
1411                             if (property.first == "UserEnabled")
1412                             {
1413                                 const bool* userEnabled =
1414                                     std::get_if<bool>(&property.second);
1415                                 if (userEnabled == nullptr)
1416                                 {
1417                                     BMCWEB_LOG_ERROR
1418                                         << "UserEnabled wasn't a bool";
1419                                     messages::internalError(asyncResp->res);
1420                                     return;
1421                                 }
1422                                 asyncResp->res.jsonValue["Enabled"] =
1423                                     *userEnabled;
1424                             }
1425                             else if (property.first ==
1426                                      "UserLockedForFailedAttempt")
1427                             {
1428                                 const bool* userLocked =
1429                                     std::get_if<bool>(&property.second);
1430                                 if (userLocked == nullptr)
1431                                 {
1432                                     BMCWEB_LOG_ERROR << "UserLockedForF"
1433                                                         "ailedAttempt "
1434                                                         "wasn't a bool";
1435                                     messages::internalError(asyncResp->res);
1436                                     return;
1437                                 }
1438                                 asyncResp->res.jsonValue["Locked"] =
1439                                     *userLocked;
1440                                 asyncResp->res.jsonValue
1441                                     ["Locked@Redfish.AllowableValues"] = {
1442                                     "false"};
1443                             }
1444                             else if (property.first == "UserPrivilege")
1445                             {
1446                                 const std::string* userPrivPtr =
1447                                     std::get_if<std::string>(&property.second);
1448                                 if (userPrivPtr == nullptr)
1449                                 {
1450                                     BMCWEB_LOG_ERROR
1451                                         << "UserPrivilege wasn't a "
1452                                            "string";
1453                                     messages::internalError(asyncResp->res);
1454                                     return;
1455                                 }
1456                                 std::string role =
1457                                     getRoleIdFromPrivilege(*userPrivPtr);
1458                                 if (role.empty())
1459                                 {
1460                                     BMCWEB_LOG_ERROR << "Invalid user role";
1461                                     messages::internalError(asyncResp->res);
1462                                     return;
1463                                 }
1464                                 asyncResp->res.jsonValue["RoleId"] = role;
1465 
1466                                 asyncResp->res.jsonValue["Links"]["Role"] = {
1467                                     {"@odata.id", "/redfish/v1/AccountService/"
1468                                                   "Roles/" +
1469                                                       role}};
1470                             }
1471                         }
1472                     }
1473                 }
1474 
1475                 asyncResp->res.jsonValue["@odata.id"] =
1476                     "/redfish/v1/AccountService/Accounts/" + accountName;
1477                 asyncResp->res.jsonValue["Id"] = accountName;
1478                 asyncResp->res.jsonValue["UserName"] = accountName;
1479             },
1480             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1481             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1482     }
1483 
1484     void doPatch(crow::Response& res, const crow::Request& req,
1485                  const std::vector<std::string>& params) override
1486     {
1487         auto asyncResp = std::make_shared<AsyncResp>(res);
1488         if (params.size() != 1)
1489         {
1490             messages::internalError(asyncResp->res);
1491             return;
1492         }
1493 
1494         std::optional<std::string> newUserName;
1495         std::optional<std::string> password;
1496         std::optional<bool> enabled;
1497         std::optional<std::string> roleId;
1498         std::optional<bool> locked;
1499         if (!json_util::readJson(req, res, "UserName", newUserName, "Password",
1500                                  password, "RoleId", roleId, "Enabled", enabled,
1501                                  "Locked", locked))
1502         {
1503             return;
1504         }
1505 
1506         const std::string& username = params[0];
1507 
1508         if (!newUserName)
1509         {
1510             // If the username isn't being updated, we can update the
1511             // properties directly
1512             updateUserProperties(asyncResp, username, password, enabled, roleId,
1513                                  locked);
1514             return;
1515         }
1516         else
1517         {
1518             crow::connections::systemBus->async_method_call(
1519                 [this, asyncResp, username, password(std::move(password)),
1520                  roleId(std::move(roleId)), enabled(std::move(enabled)),
1521                  newUser{*newUserName}, locked(std::move(locked))](
1522                     const boost::system::error_code ec) {
1523                     if (ec)
1524                     {
1525                         BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1526                         messages::resourceNotFound(
1527                             asyncResp->res,
1528                             "#ManagerAccount.v1_0_3.ManagerAccount", username);
1529                         return;
1530                     }
1531 
1532                     updateUserProperties(asyncResp, newUser, password, enabled,
1533                                          roleId, locked);
1534                 },
1535                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1536                 "xyz.openbmc_project.User.Manager", "RenameUser", username,
1537                 *newUserName);
1538         }
1539     }
1540 
1541     void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp,
1542                               const std::string& username,
1543                               std::optional<std::string> password,
1544                               std::optional<bool> enabled,
1545                               std::optional<std::string> roleId,
1546                               std::optional<bool> locked)
1547     {
1548         if (password)
1549         {
1550             if (!pamUpdatePassword(username, *password))
1551             {
1552                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1553                 messages::internalError(asyncResp->res);
1554                 return;
1555             }
1556         }
1557 
1558         std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username;
1559         dbus::utility::escapePathForDbus(dbusObjectPath);
1560 
1561         dbus::utility::checkDbusPathExists(
1562             dbusObjectPath,
1563             [dbusObjectPath(std::move(dbusObjectPath)), username,
1564              password(std::move(password)), roleId(std::move(roleId)),
1565              enabled(std::move(enabled)), locked(std::move(locked)),
1566              asyncResp{std::move(asyncResp)}](int rc) {
1567                 if (!rc)
1568                 {
1569                     messages::invalidObject(asyncResp->res, username.c_str());
1570                     return;
1571                 }
1572                 if (enabled)
1573                 {
1574                     crow::connections::systemBus->async_method_call(
1575                         [asyncResp](const boost::system::error_code ec) {
1576                             if (ec)
1577                             {
1578                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1579                                                  << ec;
1580                                 messages::internalError(asyncResp->res);
1581                                 return;
1582                             }
1583                             messages::success(asyncResp->res);
1584                             return;
1585                         },
1586                         "xyz.openbmc_project.User.Manager",
1587                         dbusObjectPath.c_str(),
1588                         "org.freedesktop.DBus.Properties", "Set",
1589                         "xyz.openbmc_project.User.Attributes", "UserEnabled",
1590                         std::variant<bool>{*enabled});
1591                 }
1592 
1593                 if (roleId)
1594                 {
1595                     std::string priv = getPrivilegeFromRoleId(*roleId);
1596                     if (priv.empty())
1597                     {
1598                         messages::propertyValueNotInList(asyncResp->res,
1599                                                          *roleId, "RoleId");
1600                         return;
1601                     }
1602 
1603                     crow::connections::systemBus->async_method_call(
1604                         [asyncResp](const boost::system::error_code ec) {
1605                             if (ec)
1606                             {
1607                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1608                                                  << ec;
1609                                 messages::internalError(asyncResp->res);
1610                                 return;
1611                             }
1612                             messages::success(asyncResp->res);
1613                         },
1614                         "xyz.openbmc_project.User.Manager",
1615                         dbusObjectPath.c_str(),
1616                         "org.freedesktop.DBus.Properties", "Set",
1617                         "xyz.openbmc_project.User.Attributes", "UserPrivilege",
1618                         std::variant<std::string>{priv});
1619                 }
1620 
1621                 if (locked)
1622                 {
1623                     // admin can unlock the account which is locked by
1624                     // successive authentication failures but admin should
1625                     // not be allowed to lock an account.
1626                     if (*locked)
1627                     {
1628                         messages::propertyValueNotInList(asyncResp->res, "true",
1629                                                          "Locked");
1630                         return;
1631                     }
1632 
1633                     crow::connections::systemBus->async_method_call(
1634                         [asyncResp](const boost::system::error_code ec) {
1635                             if (ec)
1636                             {
1637                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1638                                                  << ec;
1639                                 messages::internalError(asyncResp->res);
1640                                 return;
1641                             }
1642                             messages::success(asyncResp->res);
1643                             return;
1644                         },
1645                         "xyz.openbmc_project.User.Manager",
1646                         dbusObjectPath.c_str(),
1647                         "org.freedesktop.DBus.Properties", "Set",
1648                         "xyz.openbmc_project.User.Attributes",
1649                         "UserLockedForFailedAttempt",
1650                         sdbusplus::message::variant<bool>{*locked});
1651                 }
1652             });
1653     }
1654 
1655     void doDelete(crow::Response& res, const crow::Request& req,
1656                   const std::vector<std::string>& params) override
1657     {
1658         auto asyncResp = std::make_shared<AsyncResp>(res);
1659 
1660         if (params.size() != 1)
1661         {
1662             messages::internalError(asyncResp->res);
1663             return;
1664         }
1665 
1666         const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
1667 
1668         crow::connections::systemBus->async_method_call(
1669             [asyncResp, username{std::move(params[0])}](
1670                 const boost::system::error_code ec) {
1671                 if (ec)
1672                 {
1673                     messages::resourceNotFound(
1674                         asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
1675                         username);
1676                     return;
1677                 }
1678 
1679                 messages::accountRemoved(asyncResp->res);
1680             },
1681             "xyz.openbmc_project.User.Manager", userPath,
1682             "xyz.openbmc_project.Object.Delete", "Delete");
1683     }
1684 };
1685 
1686 } // namespace redfish
1687