xref: /openbmc/bmcweb/features/redfish/lib/account_service.hpp (revision 0ff64dc2cd3a15b4204a477ad2eb5219d66e6110)
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 "ReadOnly";
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 == "ReadOnly")
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.emplace_back(
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) :
509         Node(app, "/redfish/v1/AccountService/"), app(app)
510     {
511         entityPrivileges = {
512             {boost::beast::http::verb::get,
513              {{"ConfigureUsers"}, {"ConfigureManager"}}},
514             {boost::beast::http::verb::head, {{"Login"}}},
515             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
516             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
517             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
518             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
519     }
520 
521   private:
522     /**
523      * @brief parses the authentication section under the LDAP
524      * @param input JSON data
525      * @param asyncResp pointer to the JSON response
526      * @param userName  userName to be filled from the given JSON.
527      * @param password  password to be filled from the given JSON.
528      */
529     void
530         parseLDAPAuthenticationJson(nlohmann::json input,
531                                     const std::shared_ptr<AsyncResp>& asyncResp,
532                                     std::optional<std::string>& username,
533                                     std::optional<std::string>& password)
534     {
535         std::optional<std::string> authType;
536 
537         if (!json_util::readJson(input, asyncResp->res, "AuthenticationType",
538                                  authType, "Username", username, "Password",
539                                  password))
540         {
541             return;
542         }
543         if (!authType)
544         {
545             return;
546         }
547         if (*authType != "UsernameAndPassword")
548         {
549             messages::propertyValueNotInList(asyncResp->res, *authType,
550                                              "AuthenticationType");
551             return;
552         }
553     }
554     /**
555      * @brief parses the LDAPService section under the LDAP
556      * @param input JSON data
557      * @param asyncResp pointer to the JSON response
558      * @param baseDNList baseDN to be filled from the given JSON.
559      * @param userNameAttribute  userName to be filled from the given JSON.
560      * @param groupaAttribute  password to be filled from the given JSON.
561      */
562 
563     void parseLDAPServiceJson(
564         nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp,
565         std::optional<std::vector<std::string>>& baseDNList,
566         std::optional<std::string>& userNameAttribute,
567         std::optional<std::string>& groupsAttribute)
568     {
569         std::optional<nlohmann::json> searchSettings;
570 
571         if (!json_util::readJson(input, asyncResp->res, "SearchSettings",
572                                  searchSettings))
573         {
574             return;
575         }
576         if (!searchSettings)
577         {
578             return;
579         }
580         if (!json_util::readJson(*searchSettings, asyncResp->res,
581                                  "BaseDistinguishedNames", baseDNList,
582                                  "UsernameAttribute", userNameAttribute,
583                                  "GroupsAttribute", groupsAttribute))
584         {
585             return;
586         }
587     }
588     /**
589      * @brief updates the LDAP server address and updates the
590               json response with the new value.
591      * @param serviceAddressList address to be updated.
592      * @param asyncResp pointer to the JSON response
593      * @param ldapServerElementName Type of LDAP
594      server(openLDAP/ActiveDirectory)
595      */
596 
597     void handleServiceAddressPatch(
598         const std::vector<std::string>& serviceAddressList,
599         const std::shared_ptr<AsyncResp>& asyncResp,
600         const std::string& ldapServerElementName,
601         const std::string& ldapConfigObject)
602     {
603         crow::connections::systemBus->async_method_call(
604             [asyncResp, ldapServerElementName,
605              serviceAddressList](const boost::system::error_code ec) {
606                 if (ec)
607                 {
608                     BMCWEB_LOG_DEBUG
609                         << "Error Occured in updating the service address";
610                     messages::internalError(asyncResp->res);
611                     return;
612                 }
613                 std::vector<std::string> modifiedserviceAddressList = {
614                     serviceAddressList.front()};
615                 asyncResp->res
616                     .jsonValue[ldapServerElementName]["ServiceAddresses"] =
617                     modifiedserviceAddressList;
618                 if ((serviceAddressList).size() > 1)
619                 {
620                     messages::propertyValueModified(asyncResp->res,
621                                                     "ServiceAddresses",
622                                                     serviceAddressList.front());
623                 }
624                 BMCWEB_LOG_DEBUG << "Updated the service address";
625             },
626             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
627             ldapConfigInterface, "LDAPServerURI",
628             std::variant<std::string>(serviceAddressList.front()));
629     }
630     /**
631      * @brief updates the LDAP Bind DN and updates the
632               json response with the new value.
633      * @param username name of the user which needs to be updated.
634      * @param asyncResp pointer to the JSON response
635      * @param ldapServerElementName Type of LDAP
636      server(openLDAP/ActiveDirectory)
637      */
638 
639     void handleUserNamePatch(const std::string& username,
640                              const std::shared_ptr<AsyncResp>& asyncResp,
641                              const std::string& ldapServerElementName,
642                              const std::string& ldapConfigObject)
643     {
644         crow::connections::systemBus->async_method_call(
645             [asyncResp, username,
646              ldapServerElementName](const boost::system::error_code ec) {
647                 if (ec)
648                 {
649                     BMCWEB_LOG_DEBUG
650                         << "Error occured in updating the username";
651                     messages::internalError(asyncResp->res);
652                     return;
653                 }
654                 asyncResp->res.jsonValue[ldapServerElementName]
655                                         ["Authentication"]["Username"] =
656                     username;
657                 BMCWEB_LOG_DEBUG << "Updated the username";
658             },
659             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
660             ldapConfigInterface, "LDAPBindDN",
661             std::variant<std::string>(username));
662     }
663 
664     /**
665      * @brief updates the LDAP password
666      * @param password : ldap password which needs to be updated.
667      * @param asyncResp pointer to the JSON response
668      * @param ldapServerElementName Type of LDAP
669      *        server(openLDAP/ActiveDirectory)
670      */
671 
672     void handlePasswordPatch(const std::string& password,
673                              const std::shared_ptr<AsyncResp>& asyncResp,
674                              const std::string& ldapServerElementName,
675                              const std::string& ldapConfigObject)
676     {
677         crow::connections::systemBus->async_method_call(
678             [asyncResp, password,
679              ldapServerElementName](const boost::system::error_code ec) {
680                 if (ec)
681                 {
682                     BMCWEB_LOG_DEBUG
683                         << "Error occured in updating the password";
684                     messages::internalError(asyncResp->res);
685                     return;
686                 }
687                 asyncResp->res.jsonValue[ldapServerElementName]
688                                         ["Authentication"]["Password"] = "";
689                 BMCWEB_LOG_DEBUG << "Updated the password";
690             },
691             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
692             ldapConfigInterface, "LDAPBindDNPassword",
693             std::variant<std::string>(password));
694     }
695 
696     /**
697      * @brief updates the LDAP BaseDN and updates the
698               json response with the new value.
699      * @param baseDNList baseDN list which needs to be updated.
700      * @param asyncResp pointer to the JSON response
701      * @param ldapServerElementName Type of LDAP
702      server(openLDAP/ActiveDirectory)
703      */
704 
705     void handleBaseDNPatch(const std::vector<std::string>& baseDNList,
706                            const std::shared_ptr<AsyncResp>& asyncResp,
707                            const std::string& ldapServerElementName,
708                            const std::string& ldapConfigObject)
709     {
710         crow::connections::systemBus->async_method_call(
711             [asyncResp, baseDNList,
712              ldapServerElementName](const boost::system::error_code ec) {
713                 if (ec)
714                 {
715                     BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN";
716                     messages::internalError(asyncResp->res);
717                     return;
718                 }
719                 auto& serverTypeJson =
720                     asyncResp->res.jsonValue[ldapServerElementName];
721                 auto& searchSettingsJson =
722                     serverTypeJson["LDAPService"]["SearchSettings"];
723                 std::vector<std::string> modifiedBaseDNList = {
724                     baseDNList.front()};
725                 searchSettingsJson["BaseDistinguishedNames"] =
726                     modifiedBaseDNList;
727                 if (baseDNList.size() > 1)
728                 {
729                     messages::propertyValueModified(asyncResp->res,
730                                                     "BaseDistinguishedNames",
731                                                     baseDNList.front());
732                 }
733                 BMCWEB_LOG_DEBUG << "Updated the base DN";
734             },
735             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
736             ldapConfigInterface, "LDAPBaseDN",
737             std::variant<std::string>(baseDNList.front()));
738     }
739     /**
740      * @brief updates the LDAP user name attribute and updates the
741               json response with the new value.
742      * @param userNameAttribute attribute to be updated.
743      * @param asyncResp pointer to the JSON response
744      * @param ldapServerElementName Type of LDAP
745      server(openLDAP/ActiveDirectory)
746      */
747 
748     void handleUserNameAttrPatch(const std::string& userNameAttribute,
749                                  const std::shared_ptr<AsyncResp>& asyncResp,
750                                  const std::string& ldapServerElementName,
751                                  const std::string& ldapConfigObject)
752     {
753         crow::connections::systemBus->async_method_call(
754             [asyncResp, userNameAttribute,
755              ldapServerElementName](const boost::system::error_code ec) {
756                 if (ec)
757                 {
758                     BMCWEB_LOG_DEBUG << "Error Occured in Updating the "
759                                         "username attribute";
760                     messages::internalError(asyncResp->res);
761                     return;
762                 }
763                 auto& serverTypeJson =
764                     asyncResp->res.jsonValue[ldapServerElementName];
765                 auto& searchSettingsJson =
766                     serverTypeJson["LDAPService"]["SearchSettings"];
767                 searchSettingsJson["UsernameAttribute"] = userNameAttribute;
768                 BMCWEB_LOG_DEBUG << "Updated the user name attr.";
769             },
770             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
771             ldapConfigInterface, "UserNameAttribute",
772             std::variant<std::string>(userNameAttribute));
773     }
774     /**
775      * @brief updates the LDAP group attribute and updates the
776               json response with the new value.
777      * @param groupsAttribute attribute to be updated.
778      * @param asyncResp pointer to the JSON response
779      * @param ldapServerElementName Type of LDAP
780      server(openLDAP/ActiveDirectory)
781      */
782 
783     void handleGroupNameAttrPatch(const std::string& groupsAttribute,
784                                   const std::shared_ptr<AsyncResp>& asyncResp,
785                                   const std::string& ldapServerElementName,
786                                   const std::string& ldapConfigObject)
787     {
788         crow::connections::systemBus->async_method_call(
789             [asyncResp, groupsAttribute,
790              ldapServerElementName](const boost::system::error_code ec) {
791                 if (ec)
792                 {
793                     BMCWEB_LOG_DEBUG << "Error Occured in Updating the "
794                                         "groupname attribute";
795                     messages::internalError(asyncResp->res);
796                     return;
797                 }
798                 auto& serverTypeJson =
799                     asyncResp->res.jsonValue[ldapServerElementName];
800                 auto& searchSettingsJson =
801                     serverTypeJson["LDAPService"]["SearchSettings"];
802                 searchSettingsJson["GroupsAttribute"] = groupsAttribute;
803                 BMCWEB_LOG_DEBUG << "Updated the groupname attr";
804             },
805             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
806             ldapConfigInterface, "GroupNameAttribute",
807             std::variant<std::string>(groupsAttribute));
808     }
809     /**
810      * @brief updates the LDAP service enable and updates the
811               json response with the new value.
812      * @param input JSON data.
813      * @param asyncResp pointer to the JSON response
814      * @param ldapServerElementName Type of LDAP
815      server(openLDAP/ActiveDirectory)
816      */
817 
818     void handleServiceEnablePatch(bool serviceEnabled,
819                                   const std::shared_ptr<AsyncResp>& asyncResp,
820                                   const std::string& ldapServerElementName,
821                                   const std::string& ldapConfigObject)
822     {
823         crow::connections::systemBus->async_method_call(
824             [asyncResp, serviceEnabled,
825              ldapServerElementName](const boost::system::error_code ec) {
826                 if (ec)
827                 {
828                     BMCWEB_LOG_DEBUG
829                         << "Error Occured in Updating the service enable";
830                     messages::internalError(asyncResp->res);
831                     return;
832                 }
833                 asyncResp->res
834                     .jsonValue[ldapServerElementName]["ServiceEnabled"] =
835                     serviceEnabled;
836                 BMCWEB_LOG_DEBUG << "Updated Service enable = "
837                                  << serviceEnabled;
838             },
839             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
840             ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled));
841     }
842 
843     void handleAuthMethodsPatch(nlohmann::json& input,
844                                 const std::shared_ptr<AsyncResp>& asyncResp)
845     {
846         std::optional<bool> basicAuth;
847         std::optional<bool> cookie;
848         std::optional<bool> sessionToken;
849         std::optional<bool> xToken;
850 
851         if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth,
852                                  "Cookie", cookie, "SessionToken", sessionToken,
853                                  "XToken", xToken))
854         {
855             BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag";
856             return;
857         }
858 
859         // Make a copy of methods configuration
860         crow::persistent_data::AuthConfigMethods authMethodsConfig =
861             crow::persistent_data::SessionStore::getInstance()
862                 .getAuthMethodsConfig();
863 
864         if (basicAuth)
865         {
866             authMethodsConfig.basic = *basicAuth;
867         }
868 
869         if (cookie)
870         {
871             authMethodsConfig.cookie = *cookie;
872         }
873 
874         if (sessionToken)
875         {
876             authMethodsConfig.sessionToken = *sessionToken;
877         }
878 
879         if (xToken)
880         {
881             authMethodsConfig.xtoken = *xToken;
882         }
883 
884         if (!authMethodsConfig.basic && !authMethodsConfig.cookie &&
885             !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken)
886         {
887             // Do not allow user to disable everything
888             messages::actionNotSupported(asyncResp->res,
889                                          "of disabling all available methods");
890             return;
891         }
892 
893         crow::persistent_data::SessionStore::getInstance()
894             .updateAuthMethodsConfig(authMethodsConfig);
895         // Save configuration immediately
896         app.template getMiddleware<crow::persistent_data::Middleware>()
897             .writeData();
898 
899         messages::success(asyncResp->res);
900     }
901 
902     /**
903      * @brief Get the required values from the given JSON, validates the
904      *        value and create the LDAP config object.
905      * @param input JSON data
906      * @param asyncResp pointer to the JSON response
907      * @param serverType Type of LDAP server(openLDAP/ActiveDirectory)
908      */
909 
910     void handleLDAPPatch(nlohmann::json& input,
911                          const std::shared_ptr<AsyncResp>& asyncResp,
912                          const crow::Request& req,
913                          const std::vector<std::string>& params,
914                          const std::string& serverType)
915     {
916         std::string dbusObjectPath;
917         if (serverType == "ActiveDirectory")
918         {
919             dbusObjectPath = ADConfigObject;
920         }
921         else if (serverType == "LDAP")
922         {
923             dbusObjectPath = ldapConfigObject;
924         }
925 
926         std::optional<nlohmann::json> authentication;
927         std::optional<nlohmann::json> ldapService;
928         std::optional<std::string> accountProviderType;
929         std::optional<std::vector<std::string>> serviceAddressList;
930         std::optional<bool> serviceEnabled;
931         std::optional<std::vector<std::string>> baseDNList;
932         std::optional<std::string> userNameAttribute;
933         std::optional<std::string> groupsAttribute;
934         std::optional<std::string> userName;
935         std::optional<std::string> password;
936         std::optional<std::vector<nlohmann::json>> remoteRoleMapData;
937 
938         if (!json_util::readJson(input, asyncResp->res, "Authentication",
939                                  authentication, "LDAPService", ldapService,
940                                  "ServiceAddresses", serviceAddressList,
941                                  "AccountProviderType", accountProviderType,
942                                  "ServiceEnabled", serviceEnabled,
943                                  "RemoteRoleMapping", remoteRoleMapData))
944         {
945             return;
946         }
947 
948         if (authentication)
949         {
950             parseLDAPAuthenticationJson(*authentication, asyncResp, userName,
951                                         password);
952         }
953         if (ldapService)
954         {
955             parseLDAPServiceJson(*ldapService, asyncResp, baseDNList,
956                                  userNameAttribute, groupsAttribute);
957         }
958         if (accountProviderType)
959         {
960             messages::propertyNotWritable(asyncResp->res,
961                                           "AccountProviderType");
962         }
963         if (serviceAddressList)
964         {
965             if ((*serviceAddressList).size() == 0)
966             {
967                 messages::propertyValueNotInList(asyncResp->res, "[]",
968                                                  "ServiceAddress");
969                 return;
970             }
971         }
972         if (baseDNList)
973         {
974             if ((*baseDNList).size() == 0)
975             {
976                 messages::propertyValueNotInList(asyncResp->res, "[]",
977                                                  "BaseDistinguishedNames");
978                 return;
979             }
980         }
981 
982         // nothing to update, then return
983         if (!userName && !password && !serviceAddressList && !baseDNList &&
984             !userNameAttribute && !groupsAttribute && !serviceEnabled &&
985             !remoteRoleMapData)
986         {
987             return;
988         }
989 
990         // Get the existing resource first then keep modifying
991         // whenever any property gets updated.
992         getLDAPConfigData(serverType, [this, asyncResp, userName, password,
993                                        baseDNList, userNameAttribute,
994                                        groupsAttribute, accountProviderType,
995                                        serviceAddressList, serviceEnabled,
996                                        dbusObjectPath, remoteRoleMapData](
997                                           bool success, LDAPConfigData confData,
998                                           const std::string& serverType) {
999             if (!success)
1000             {
1001                 messages::internalError(asyncResp->res);
1002                 return;
1003             }
1004             parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType);
1005             if (confData.serviceEnabled)
1006             {
1007                 // Disable the service first and update the rest of
1008                 // the properties.
1009                 handleServiceEnablePatch(false, asyncResp, serverType,
1010                                          dbusObjectPath);
1011             }
1012 
1013             if (serviceAddressList)
1014             {
1015                 handleServiceAddressPatch(*serviceAddressList, asyncResp,
1016                                           serverType, dbusObjectPath);
1017             }
1018             if (userName)
1019             {
1020                 handleUserNamePatch(*userName, asyncResp, serverType,
1021                                     dbusObjectPath);
1022             }
1023             if (password)
1024             {
1025                 handlePasswordPatch(*password, asyncResp, serverType,
1026                                     dbusObjectPath);
1027             }
1028 
1029             if (baseDNList)
1030             {
1031                 handleBaseDNPatch(*baseDNList, asyncResp, serverType,
1032                                   dbusObjectPath);
1033             }
1034             if (userNameAttribute)
1035             {
1036                 handleUserNameAttrPatch(*userNameAttribute, asyncResp,
1037                                         serverType, dbusObjectPath);
1038             }
1039             if (groupsAttribute)
1040             {
1041                 handleGroupNameAttrPatch(*groupsAttribute, asyncResp,
1042                                          serverType, dbusObjectPath);
1043             }
1044             if (serviceEnabled)
1045             {
1046                 // if user has given the value as true then enable
1047                 // the service. if user has given false then no-op
1048                 // as service is already stopped.
1049                 if (*serviceEnabled)
1050                 {
1051                     handleServiceEnablePatch(*serviceEnabled, asyncResp,
1052                                              serverType, dbusObjectPath);
1053                 }
1054             }
1055             else
1056             {
1057                 // if user has not given the service enabled value
1058                 // then revert it to the same state as it was
1059                 // before.
1060                 handleServiceEnablePatch(confData.serviceEnabled, asyncResp,
1061                                          serverType, dbusObjectPath);
1062             }
1063 
1064             if (remoteRoleMapData)
1065             {
1066                 std::vector<nlohmann::json> remoteRoleMap =
1067                     std::move(*remoteRoleMapData);
1068 
1069                 handleRoleMapPatch(asyncResp, confData.groupRoleList,
1070                                    serverType, remoteRoleMap);
1071             }
1072         });
1073     }
1074 
1075     void doGet(crow::Response& res, const crow::Request& req,
1076                const std::vector<std::string>& params) override
1077     {
1078         const crow::persistent_data::AuthConfigMethods& authMethodsConfig =
1079             crow::persistent_data::SessionStore::getInstance()
1080                 .getAuthMethodsConfig();
1081 
1082         auto asyncResp = std::make_shared<AsyncResp>(res);
1083         res.jsonValue = {
1084             {"@odata.context", "/redfish/v1/"
1085                                "$metadata#AccountService.AccountService"},
1086             {"@odata.id", "/redfish/v1/AccountService"},
1087             {"@odata.type", "#AccountService."
1088                             "v1_4_0.AccountService"},
1089             {"Id", "AccountService"},
1090             {"Name", "Account Service"},
1091             {"Description", "Account Service"},
1092             {"ServiceEnabled", true},
1093             {"MaxPasswordLength", 20},
1094             {"Accounts",
1095              {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}},
1096             {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}},
1097             {"Oem",
1098              {{"OpenBMC",
1099                {{"@odata.type", "#OemAccountService.v1_0_0.AccountService"},
1100                 {"AuthMethods",
1101                  {
1102                      {"BasicAuth", authMethodsConfig.basic},
1103                      {"SessionToken", authMethodsConfig.sessionToken},
1104                      {"XToken", authMethodsConfig.xtoken},
1105                      {"Cookie", authMethodsConfig.cookie},
1106                  }}}}}},
1107             {"LDAP",
1108              {{"Certificates",
1109                {{"@odata.id",
1110                  "/redfish/v1/AccountService/LDAP/Certificates"}}}}}};
1111         crow::connections::systemBus->async_method_call(
1112             [asyncResp](
1113                 const boost::system::error_code ec,
1114                 const std::vector<std::pair<
1115                     std::string, std::variant<uint32_t, uint16_t, uint8_t>>>&
1116                     propertiesList) {
1117                 if (ec)
1118                 {
1119                     messages::internalError(asyncResp->res);
1120                     return;
1121                 }
1122                 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
1123                                  << "properties for AccountService";
1124                 for (const std::pair<std::string,
1125                                      std::variant<uint32_t, uint16_t, uint8_t>>&
1126                          property : propertiesList)
1127                 {
1128                     if (property.first == "MinPasswordLength")
1129                     {
1130                         const uint8_t* value =
1131                             std::get_if<uint8_t>(&property.second);
1132                         if (value != nullptr)
1133                         {
1134                             asyncResp->res.jsonValue["MinPasswordLength"] =
1135                                 *value;
1136                         }
1137                     }
1138                     if (property.first == "AccountUnlockTimeout")
1139                     {
1140                         const uint32_t* value =
1141                             std::get_if<uint32_t>(&property.second);
1142                         if (value != nullptr)
1143                         {
1144                             asyncResp->res.jsonValue["AccountLockoutDuration"] =
1145                                 *value;
1146                         }
1147                     }
1148                     if (property.first == "MaxLoginAttemptBeforeLockout")
1149                     {
1150                         const uint16_t* value =
1151                             std::get_if<uint16_t>(&property.second);
1152                         if (value != nullptr)
1153                         {
1154                             asyncResp->res
1155                                 .jsonValue["AccountLockoutThreshold"] = *value;
1156                         }
1157                     }
1158                 }
1159             },
1160             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1161             "org.freedesktop.DBus.Properties", "GetAll",
1162             "xyz.openbmc_project.User.AccountPolicy");
1163 
1164         auto callback = [asyncResp](bool success, LDAPConfigData& confData,
1165                                     const std::string& ldapType) {
1166             parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1167         };
1168 
1169         getLDAPConfigData("LDAP", callback);
1170         getLDAPConfigData("ActiveDirectory", callback);
1171     }
1172 
1173     void doPatch(crow::Response& res, const crow::Request& req,
1174                  const std::vector<std::string>& params) override
1175     {
1176         auto asyncResp = std::make_shared<AsyncResp>(res);
1177 
1178         std::optional<uint32_t> unlockTimeout;
1179         std::optional<uint16_t> lockoutThreshold;
1180         std::optional<uint16_t> minPasswordLength;
1181         std::optional<uint16_t> maxPasswordLength;
1182         std::optional<nlohmann::json> ldapObject;
1183         std::optional<nlohmann::json> activeDirectoryObject;
1184         std::optional<nlohmann::json> oemObject;
1185 
1186         if (!json_util::readJson(
1187                 req, res, "AccountLockoutDuration", unlockTimeout,
1188                 "AccountLockoutThreshold", lockoutThreshold,
1189                 "MaxPasswordLength", maxPasswordLength, "MinPasswordLength",
1190                 minPasswordLength, "LDAP", ldapObject, "ActiveDirectory",
1191                 activeDirectoryObject, "Oem", oemObject))
1192         {
1193             return;
1194         }
1195 
1196         if (minPasswordLength)
1197         {
1198             messages::propertyNotWritable(asyncResp->res, "MinPasswordLength");
1199         }
1200 
1201         if (maxPasswordLength)
1202         {
1203             messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1204         }
1205 
1206         if (ldapObject)
1207         {
1208             handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP");
1209         }
1210 
1211         if (std::optional<nlohmann::json> oemOpenBMCObject;
1212             oemObject &&
1213             json_util::readJson(*oemObject, res, "OpenBMC", oemOpenBMCObject))
1214         {
1215             if (std::optional<nlohmann::json> authMethodsObject;
1216                 oemOpenBMCObject &&
1217                 json_util::readJson(*oemOpenBMCObject, res, "AuthMethods",
1218                                     authMethodsObject))
1219             {
1220                 if (authMethodsObject)
1221                 {
1222                     handleAuthMethodsPatch(*authMethodsObject, asyncResp);
1223                 }
1224             }
1225         }
1226 
1227         if (activeDirectoryObject)
1228         {
1229             handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params,
1230                             "ActiveDirectory");
1231         }
1232 
1233         if (unlockTimeout)
1234         {
1235             crow::connections::systemBus->async_method_call(
1236                 [asyncResp](const boost::system::error_code ec) {
1237                     if (ec)
1238                     {
1239                         messages::internalError(asyncResp->res);
1240                         return;
1241                     }
1242                     messages::success(asyncResp->res);
1243                 },
1244                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1245                 "org.freedesktop.DBus.Properties", "Set",
1246                 "xyz.openbmc_project.User.AccountPolicy",
1247                 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout));
1248         }
1249         if (lockoutThreshold)
1250         {
1251             crow::connections::systemBus->async_method_call(
1252                 [asyncResp](const boost::system::error_code ec) {
1253                     if (ec)
1254                     {
1255                         messages::internalError(asyncResp->res);
1256                         return;
1257                     }
1258                     messages::success(asyncResp->res);
1259                 },
1260                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1261                 "org.freedesktop.DBus.Properties", "Set",
1262                 "xyz.openbmc_project.User.AccountPolicy",
1263                 "MaxLoginAttemptBeforeLockout",
1264                 std::variant<uint16_t>(*lockoutThreshold));
1265         }
1266     }
1267 
1268     CrowApp& app;
1269 };
1270 
1271 class AccountsCollection : public Node
1272 {
1273   public:
1274     AccountsCollection(CrowApp& app) :
1275         Node(app, "/redfish/v1/AccountService/Accounts/")
1276     {
1277         entityPrivileges = {
1278             {boost::beast::http::verb::get,
1279              {{"ConfigureUsers"}, {"ConfigureManager"}}},
1280             {boost::beast::http::verb::head, {{"Login"}}},
1281             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
1282             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1283             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1284             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1285     }
1286 
1287   private:
1288     void doGet(crow::Response& res, const crow::Request& req,
1289                const std::vector<std::string>& params) override
1290     {
1291         auto asyncResp = std::make_shared<AsyncResp>(res);
1292         res.jsonValue = {{"@odata.context",
1293                           "/redfish/v1/"
1294                           "$metadata#ManagerAccountCollection."
1295                           "ManagerAccountCollection"},
1296                          {"@odata.id", "/redfish/v1/AccountService/Accounts"},
1297                          {"@odata.type", "#ManagerAccountCollection."
1298                                          "ManagerAccountCollection"},
1299                          {"Name", "Accounts Collection"},
1300                          {"Description", "BMC User Accounts"}};
1301 
1302         crow::connections::systemBus->async_method_call(
1303             [asyncResp](const boost::system::error_code ec,
1304                         const ManagedObjectType& users) {
1305                 if (ec)
1306                 {
1307                     messages::internalError(asyncResp->res);
1308                     return;
1309                 }
1310 
1311                 nlohmann::json& memberArray =
1312                     asyncResp->res.jsonValue["Members"];
1313                 memberArray = nlohmann::json::array();
1314 
1315                 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
1316                 for (auto& user : users)
1317                 {
1318                     const std::string& path =
1319                         static_cast<const std::string&>(user.first);
1320                     std::size_t lastIndex = path.rfind("/");
1321                     if (lastIndex == std::string::npos)
1322                     {
1323                         lastIndex = 0;
1324                     }
1325                     else
1326                     {
1327                         lastIndex += 1;
1328                     }
1329                     memberArray.push_back(
1330                         {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
1331                                            path.substr(lastIndex)}});
1332                 }
1333             },
1334             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1335             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1336     }
1337     void doPost(crow::Response& res, const crow::Request& req,
1338                 const std::vector<std::string>& params) override
1339     {
1340         auto asyncResp = std::make_shared<AsyncResp>(res);
1341 
1342         std::string username;
1343         std::string password;
1344         std::optional<std::string> roleId("User");
1345         std::optional<bool> enabled = true;
1346         if (!json_util::readJson(req, res, "UserName", username, "Password",
1347                                  password, "RoleId", roleId, "Enabled",
1348                                  enabled))
1349         {
1350             return;
1351         }
1352 
1353         std::string priv = getPrivilegeFromRoleId(*roleId);
1354         if (priv.empty())
1355         {
1356             messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
1357             return;
1358         }
1359         roleId = priv;
1360 
1361         // Reading AllGroups property
1362         crow::connections::systemBus->async_method_call(
1363             [asyncResp, username, password{std::move(password)}, roleId,
1364              enabled](const boost::system::error_code ec,
1365                       const std::variant<std::vector<std::string>>& allGroups) {
1366                 if (ec)
1367                 {
1368                     BMCWEB_LOG_DEBUG << "ERROR with async_method_call";
1369                     messages::internalError(asyncResp->res);
1370                     return;
1371                 }
1372 
1373                 const std::vector<std::string>* allGroupsList =
1374                     std::get_if<std::vector<std::string>>(&allGroups);
1375 
1376                 if (allGroupsList == nullptr || allGroupsList->empty())
1377                 {
1378                     messages::internalError(asyncResp->res);
1379                     return;
1380                 }
1381 
1382                 crow::connections::systemBus->async_method_call(
1383                     [asyncResp, username, password{std::move(password)}](
1384                         const boost::system::error_code ec) {
1385                         if (ec)
1386                         {
1387                             messages::resourceAlreadyExists(
1388                                 asyncResp->res,
1389                                 "#ManagerAccount.v1_0_3.ManagerAccount",
1390                                 "UserName", username);
1391                             return;
1392                         }
1393 
1394                         if (!pamUpdatePassword(username, password))
1395                         {
1396                             // At this point we have a user that's been created,
1397                             // but the password set failed.Something is wrong,
1398                             // so delete the user that we've already created
1399                             crow::connections::systemBus->async_method_call(
1400                                 [asyncResp](
1401                                     const boost::system::error_code ec) {
1402                                     if (ec)
1403                                     {
1404                                         messages::internalError(asyncResp->res);
1405                                         return;
1406                                     }
1407 
1408                                     messages::invalidObject(asyncResp->res,
1409                                                             "Password");
1410                                 },
1411                                 "xyz.openbmc_project.User.Manager",
1412                                 "/xyz/openbmc_project/user/" + username,
1413                                 "xyz.openbmc_project.Object.Delete", "Delete");
1414 
1415                             BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1416                             return;
1417                         }
1418 
1419                         messages::created(asyncResp->res);
1420                         asyncResp->res.addHeader(
1421                             "Location",
1422                             "/redfish/v1/AccountService/Accounts/" + username);
1423                     },
1424                     "xyz.openbmc_project.User.Manager",
1425                     "/xyz/openbmc_project/user",
1426                     "xyz.openbmc_project.User.Manager", "CreateUser", username,
1427                     *allGroupsList, *roleId, *enabled);
1428             },
1429             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1430             "org.freedesktop.DBus.Properties", "Get",
1431             "xyz.openbmc_project.User.Manager", "AllGroups");
1432     }
1433 };
1434 
1435 class ManagerAccount : public Node
1436 {
1437   public:
1438     ManagerAccount(CrowApp& app) :
1439         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
1440     {
1441         entityPrivileges = {
1442             {boost::beast::http::verb::get,
1443              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
1444             {boost::beast::http::verb::head, {{"Login"}}},
1445             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
1446             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1447             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1448             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1449     }
1450 
1451   private:
1452     void doGet(crow::Response& res, const crow::Request& req,
1453                const std::vector<std::string>& params) override
1454     {
1455 
1456         auto asyncResp = std::make_shared<AsyncResp>(res);
1457 
1458         if (params.size() != 1)
1459         {
1460             messages::internalError(asyncResp->res);
1461             return;
1462         }
1463 
1464         crow::connections::systemBus->async_method_call(
1465             [asyncResp, accountName{std::string(params[0])}](
1466                 const boost::system::error_code ec,
1467                 const ManagedObjectType& users) {
1468                 if (ec)
1469                 {
1470                     messages::internalError(asyncResp->res);
1471                     return;
1472                 }
1473                 auto userIt = users.begin();
1474 
1475                 for (; userIt != users.end(); userIt++)
1476                 {
1477                     if (boost::ends_with(userIt->first.str, "/" + accountName))
1478                     {
1479                         break;
1480                     }
1481                 }
1482                 if (userIt == users.end())
1483                 {
1484                     messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1485                                                accountName);
1486                     return;
1487                 }
1488 
1489                 asyncResp->res.jsonValue = {
1490                     {"@odata.context",
1491                      "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
1492                     {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
1493                     {"Name", "User Account"},
1494                     {"Description", "User Account"},
1495                     {"Password", nullptr}};
1496 
1497                 for (const auto& interface : userIt->second)
1498                 {
1499                     if (interface.first ==
1500                         "xyz.openbmc_project.User.Attributes")
1501                     {
1502                         for (const auto& property : interface.second)
1503                         {
1504                             if (property.first == "UserEnabled")
1505                             {
1506                                 const bool* userEnabled =
1507                                     std::get_if<bool>(&property.second);
1508                                 if (userEnabled == nullptr)
1509                                 {
1510                                     BMCWEB_LOG_ERROR
1511                                         << "UserEnabled wasn't a bool";
1512                                     messages::internalError(asyncResp->res);
1513                                     return;
1514                                 }
1515                                 asyncResp->res.jsonValue["Enabled"] =
1516                                     *userEnabled;
1517                             }
1518                             else if (property.first ==
1519                                      "UserLockedForFailedAttempt")
1520                             {
1521                                 const bool* userLocked =
1522                                     std::get_if<bool>(&property.second);
1523                                 if (userLocked == nullptr)
1524                                 {
1525                                     BMCWEB_LOG_ERROR << "UserLockedForF"
1526                                                         "ailedAttempt "
1527                                                         "wasn't a bool";
1528                                     messages::internalError(asyncResp->res);
1529                                     return;
1530                                 }
1531                                 asyncResp->res.jsonValue["Locked"] =
1532                                     *userLocked;
1533                                 asyncResp->res.jsonValue
1534                                     ["Locked@Redfish.AllowableValues"] = {
1535                                     "false"};
1536                             }
1537                             else if (property.first == "UserPrivilege")
1538                             {
1539                                 const std::string* userPrivPtr =
1540                                     std::get_if<std::string>(&property.second);
1541                                 if (userPrivPtr == nullptr)
1542                                 {
1543                                     BMCWEB_LOG_ERROR
1544                                         << "UserPrivilege wasn't a "
1545                                            "string";
1546                                     messages::internalError(asyncResp->res);
1547                                     return;
1548                                 }
1549                                 std::string role =
1550                                     getRoleIdFromPrivilege(*userPrivPtr);
1551                                 if (role.empty())
1552                                 {
1553                                     BMCWEB_LOG_ERROR << "Invalid user role";
1554                                     messages::internalError(asyncResp->res);
1555                                     return;
1556                                 }
1557                                 asyncResp->res.jsonValue["RoleId"] = role;
1558 
1559                                 asyncResp->res.jsonValue["Links"]["Role"] = {
1560                                     {"@odata.id", "/redfish/v1/AccountService/"
1561                                                   "Roles/" +
1562                                                       role}};
1563                             }
1564                         }
1565                     }
1566                 }
1567 
1568                 asyncResp->res.jsonValue["@odata.id"] =
1569                     "/redfish/v1/AccountService/Accounts/" + accountName;
1570                 asyncResp->res.jsonValue["Id"] = accountName;
1571                 asyncResp->res.jsonValue["UserName"] = accountName;
1572             },
1573             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1574             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1575     }
1576 
1577     void doPatch(crow::Response& res, const crow::Request& req,
1578                  const std::vector<std::string>& params) override
1579     {
1580         auto asyncResp = std::make_shared<AsyncResp>(res);
1581         if (params.size() != 1)
1582         {
1583             messages::internalError(asyncResp->res);
1584             return;
1585         }
1586 
1587         std::optional<std::string> newUserName;
1588         std::optional<std::string> password;
1589         std::optional<bool> enabled;
1590         std::optional<std::string> roleId;
1591         std::optional<bool> locked;
1592         if (!json_util::readJson(req, res, "UserName", newUserName, "Password",
1593                                  password, "RoleId", roleId, "Enabled", enabled,
1594                                  "Locked", locked))
1595         {
1596             return;
1597         }
1598 
1599         const std::string& username = params[0];
1600 
1601         if (!newUserName)
1602         {
1603             // If the username isn't being updated, we can update the
1604             // properties directly
1605             updateUserProperties(asyncResp, username, password, enabled, roleId,
1606                                  locked);
1607             return;
1608         }
1609         else
1610         {
1611             crow::connections::systemBus->async_method_call(
1612                 [this, asyncResp, username, password(std::move(password)),
1613                  roleId(std::move(roleId)), enabled(std::move(enabled)),
1614                  newUser{*newUserName}, locked(std::move(locked))](
1615                     const boost::system::error_code ec) {
1616                     if (ec)
1617                     {
1618                         BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1619                         messages::resourceNotFound(
1620                             asyncResp->res,
1621                             "#ManagerAccount.v1_0_3.ManagerAccount", username);
1622                         return;
1623                     }
1624 
1625                     updateUserProperties(asyncResp, newUser, password, enabled,
1626                                          roleId, locked);
1627                 },
1628                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1629                 "xyz.openbmc_project.User.Manager", "RenameUser", username,
1630                 *newUserName);
1631         }
1632     }
1633 
1634     void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp,
1635                               const std::string& username,
1636                               std::optional<std::string> password,
1637                               std::optional<bool> enabled,
1638                               std::optional<std::string> roleId,
1639                               std::optional<bool> locked)
1640     {
1641         if (password)
1642         {
1643             if (!pamUpdatePassword(username, *password))
1644             {
1645                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1646                 messages::internalError(asyncResp->res);
1647                 return;
1648             }
1649         }
1650 
1651         std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username;
1652         dbus::utility::escapePathForDbus(dbusObjectPath);
1653 
1654         dbus::utility::checkDbusPathExists(
1655             dbusObjectPath,
1656             [dbusObjectPath(std::move(dbusObjectPath)), username,
1657              password(std::move(password)), roleId(std::move(roleId)),
1658              enabled(std::move(enabled)), locked(std::move(locked)),
1659              asyncResp{std::move(asyncResp)}](int rc) {
1660                 if (!rc)
1661                 {
1662                     messages::invalidObject(asyncResp->res, username.c_str());
1663                     return;
1664                 }
1665                 if (enabled)
1666                 {
1667                     crow::connections::systemBus->async_method_call(
1668                         [asyncResp](const boost::system::error_code ec) {
1669                             if (ec)
1670                             {
1671                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1672                                                  << ec;
1673                                 messages::internalError(asyncResp->res);
1674                                 return;
1675                             }
1676                             messages::success(asyncResp->res);
1677                             return;
1678                         },
1679                         "xyz.openbmc_project.User.Manager",
1680                         dbusObjectPath.c_str(),
1681                         "org.freedesktop.DBus.Properties", "Set",
1682                         "xyz.openbmc_project.User.Attributes", "UserEnabled",
1683                         std::variant<bool>{*enabled});
1684                 }
1685 
1686                 if (roleId)
1687                 {
1688                     std::string priv = getPrivilegeFromRoleId(*roleId);
1689                     if (priv.empty())
1690                     {
1691                         messages::propertyValueNotInList(asyncResp->res,
1692                                                          *roleId, "RoleId");
1693                         return;
1694                     }
1695 
1696                     crow::connections::systemBus->async_method_call(
1697                         [asyncResp](const boost::system::error_code ec) {
1698                             if (ec)
1699                             {
1700                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1701                                                  << ec;
1702                                 messages::internalError(asyncResp->res);
1703                                 return;
1704                             }
1705                             messages::success(asyncResp->res);
1706                         },
1707                         "xyz.openbmc_project.User.Manager",
1708                         dbusObjectPath.c_str(),
1709                         "org.freedesktop.DBus.Properties", "Set",
1710                         "xyz.openbmc_project.User.Attributes", "UserPrivilege",
1711                         std::variant<std::string>{priv});
1712                 }
1713 
1714                 if (locked)
1715                 {
1716                     // admin can unlock the account which is locked by
1717                     // successive authentication failures but admin should
1718                     // not be allowed to lock an account.
1719                     if (*locked)
1720                     {
1721                         messages::propertyValueNotInList(asyncResp->res, "true",
1722                                                          "Locked");
1723                         return;
1724                     }
1725 
1726                     crow::connections::systemBus->async_method_call(
1727                         [asyncResp](const boost::system::error_code ec) {
1728                             if (ec)
1729                             {
1730                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1731                                                  << ec;
1732                                 messages::internalError(asyncResp->res);
1733                                 return;
1734                             }
1735                             messages::success(asyncResp->res);
1736                             return;
1737                         },
1738                         "xyz.openbmc_project.User.Manager",
1739                         dbusObjectPath.c_str(),
1740                         "org.freedesktop.DBus.Properties", "Set",
1741                         "xyz.openbmc_project.User.Attributes",
1742                         "UserLockedForFailedAttempt",
1743                         sdbusplus::message::variant<bool>{*locked});
1744                 }
1745             });
1746     }
1747 
1748     void doDelete(crow::Response& res, const crow::Request& req,
1749                   const std::vector<std::string>& params) override
1750     {
1751         auto asyncResp = std::make_shared<AsyncResp>(res);
1752 
1753         if (params.size() != 1)
1754         {
1755             messages::internalError(asyncResp->res);
1756             return;
1757         }
1758 
1759         const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
1760 
1761         crow::connections::systemBus->async_method_call(
1762             [asyncResp, username{std::move(params[0])}](
1763                 const boost::system::error_code ec) {
1764                 if (ec)
1765                 {
1766                     messages::resourceNotFound(
1767                         asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
1768                         username);
1769                     return;
1770                 }
1771 
1772                 messages::accountRemoved(asyncResp->res);
1773             },
1774             "xyz.openbmc_project.User.Manager", userPath,
1775             "xyz.openbmc_project.Object.Delete", "Delete");
1776     }
1777 };
1778 
1779 } // namespace redfish
1780