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