xref: /openbmc/bmcweb/features/redfish/lib/account_service.hpp (revision eb2bbe56e4cce00f92d2f26fb66bfefefa28f929)
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::string dbusObjectPath;
640         if (serverType == "ActiveDirectory")
641         {
642             dbusObjectPath = ADConfigObject;
643         }
644         else if (serverType == "LDAP")
645         {
646             dbusObjectPath = ldapConfigObject;
647         }
648 
649         std::optional<nlohmann::json> authentication;
650         std::optional<nlohmann::json> ldapService;
651         std::optional<std::string> accountProviderType;
652         std::optional<std::vector<std::string>> serviceAddressList;
653         std::optional<bool> serviceEnabled;
654         std::optional<std::vector<std::string>> baseDNList;
655         std::optional<std::string> userNameAttribute;
656         std::optional<std::string> groupsAttribute;
657         std::optional<std::string> userName;
658         std::optional<std::string> password;
659 
660         if (!json_util::readJson(input, asyncResp->res, "Authentication",
661                                  authentication, "LDAPService", ldapService,
662                                  "ServiceAddresses", serviceAddressList,
663                                  "AccountProviderType", accountProviderType,
664                                  "ServiceEnabled", serviceEnabled))
665         {
666             return;
667         }
668 
669         if (authentication)
670         {
671             parseLDAPAuthenticationJson(*authentication, asyncResp, userName,
672                                         password);
673         }
674         if (ldapService)
675         {
676             parseLDAPServiceJson(*ldapService, asyncResp, baseDNList,
677                                  userNameAttribute, groupsAttribute);
678         }
679         if (accountProviderType)
680         {
681             messages::propertyNotWritable(asyncResp->res,
682                                           "AccountProviderType");
683         }
684         if (serviceAddressList)
685         {
686             if ((*serviceAddressList).size() == 0)
687             {
688                 messages::propertyValueNotInList(asyncResp->res, "[]",
689                                                  "ServiceAddress");
690                 return;
691             }
692         }
693         if (baseDNList)
694         {
695             if ((*baseDNList).size() == 0)
696             {
697                 messages::propertyValueNotInList(asyncResp->res, "[]",
698                                                  "BaseDistinguishedNames");
699                 return;
700             }
701         }
702 
703         // nothing to update, then return
704         if (!userName && !password && !serviceAddressList && !baseDNList &&
705             !userNameAttribute && !groupsAttribute && !serviceEnabled)
706         {
707             return;
708         }
709 
710         // Get the existing resource first then keep modifying
711         // whenever any property gets updated.
712         getLDAPConfigData(serverType, [this, asyncResp, userName, password,
713                                        baseDNList, userNameAttribute,
714                                        groupsAttribute, accountProviderType,
715                                        serviceAddressList, serviceEnabled,
716                                        dbusObjectPath](
717                                           bool success, LDAPConfigData confData,
718                                           const std::string& serverType) {
719             if (!success)
720             {
721                 messages::internalError(asyncResp->res);
722                 return;
723             }
724             parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType);
725             if (confData.serviceEnabled)
726             {
727                 // Disable the service first and update the rest of
728                 // the properties.
729                 handleServiceEnablePatch(false, asyncResp, serverType,
730                                          dbusObjectPath);
731             }
732 
733             if (serviceAddressList)
734             {
735                 handleServiceAddressPatch(*serviceAddressList, asyncResp,
736                                           serverType, dbusObjectPath);
737             }
738             if (userName)
739             {
740                 handleUserNamePatch(*userName, asyncResp, serverType,
741                                     dbusObjectPath);
742             }
743             if (password)
744             {
745                 handlePasswordPatch(*password, asyncResp, serverType,
746                                     dbusObjectPath);
747             }
748 
749             if (baseDNList)
750             {
751                 handleBaseDNPatch(*baseDNList, asyncResp, serverType,
752                                   dbusObjectPath);
753             }
754             if (userNameAttribute)
755             {
756                 handleUserNameAttrPatch(*userNameAttribute, asyncResp,
757                                         serverType, dbusObjectPath);
758             }
759             if (groupsAttribute)
760             {
761                 handleGroupNameAttrPatch(*groupsAttribute, asyncResp,
762                                          serverType, dbusObjectPath);
763             }
764             if (serviceEnabled)
765             {
766                 // if user has given the value as true then enable
767                 // the service. if user has given false then no-op
768                 // as service is already stopped.
769                 if (*serviceEnabled)
770                 {
771                     handleServiceEnablePatch(*serviceEnabled, asyncResp,
772                                              serverType, dbusObjectPath);
773                 }
774             }
775             else
776             {
777                 // if user has not given the service enabled value
778                 // then revert it to the same state as it was
779                 // before.
780                 handleServiceEnablePatch(confData.serviceEnabled, asyncResp,
781                                          serverType, dbusObjectPath);
782             }
783         });
784     }
785 
786     void doGet(crow::Response& res, const crow::Request& req,
787                const std::vector<std::string>& params) override
788     {
789         auto asyncResp = std::make_shared<AsyncResp>(res);
790         res.jsonValue = {
791             {"@odata.context", "/redfish/v1/"
792                                "$metadata#AccountService.AccountService"},
793             {"@odata.id", "/redfish/v1/AccountService"},
794             {"@odata.type", "#AccountService."
795                             "v1_3_1.AccountService"},
796             {"Id", "AccountService"},
797             {"Name", "Account Service"},
798             {"Description", "Account Service"},
799             {"ServiceEnabled", true},
800             {"MaxPasswordLength", 20},
801             {"Accounts",
802              {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}},
803             {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}};
804 
805         crow::connections::systemBus->async_method_call(
806             [asyncResp](
807                 const boost::system::error_code ec,
808                 const std::vector<std::pair<
809                     std::string, std::variant<uint32_t, uint16_t, uint8_t>>>&
810                     propertiesList) {
811                 if (ec)
812                 {
813                     messages::internalError(asyncResp->res);
814                     return;
815                 }
816                 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
817                                  << "properties for AccountService";
818                 for (const std::pair<std::string,
819                                      std::variant<uint32_t, uint16_t, uint8_t>>&
820                          property : propertiesList)
821                 {
822                     if (property.first == "MinPasswordLength")
823                     {
824                         const uint8_t* value =
825                             std::get_if<uint8_t>(&property.second);
826                         if (value != nullptr)
827                         {
828                             asyncResp->res.jsonValue["MinPasswordLength"] =
829                                 *value;
830                         }
831                     }
832                     if (property.first == "AccountUnlockTimeout")
833                     {
834                         const uint32_t* value =
835                             std::get_if<uint32_t>(&property.second);
836                         if (value != nullptr)
837                         {
838                             asyncResp->res.jsonValue["AccountLockoutDuration"] =
839                                 *value;
840                         }
841                     }
842                     if (property.first == "MaxLoginAttemptBeforeLockout")
843                     {
844                         const uint16_t* value =
845                             std::get_if<uint16_t>(&property.second);
846                         if (value != nullptr)
847                         {
848                             asyncResp->res
849                                 .jsonValue["AccountLockoutThreshold"] = *value;
850                         }
851                     }
852                 }
853             },
854             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
855             "org.freedesktop.DBus.Properties", "GetAll",
856             "xyz.openbmc_project.User.AccountPolicy");
857 
858         auto callback = [asyncResp](bool success, LDAPConfigData& confData,
859                                     const std::string& ldapType) {
860             parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
861         };
862 
863         getLDAPConfigData("LDAP", callback);
864         getLDAPConfigData("ActiveDirectory", callback);
865     }
866 
867     void doPatch(crow::Response& res, const crow::Request& req,
868                  const std::vector<std::string>& params) override
869     {
870         auto asyncResp = std::make_shared<AsyncResp>(res);
871 
872         std::optional<uint32_t> unlockTimeout;
873         std::optional<uint16_t> lockoutThreshold;
874         std::optional<uint16_t> minPasswordLength;
875         std::optional<uint16_t> maxPasswordLength;
876         std::optional<nlohmann::json> ldapObject;
877         std::optional<nlohmann::json> activeDirectoryObject;
878 
879         if (!json_util::readJson(req, res, "AccountLockoutDuration",
880                                  unlockTimeout, "AccountLockoutThreshold",
881                                  lockoutThreshold, "MaxPasswordLength",
882                                  maxPasswordLength, "MinPasswordLength",
883                                  minPasswordLength, "LDAP", ldapObject,
884                                  "ActiveDirectory", activeDirectoryObject))
885         {
886             return;
887         }
888 
889         if (minPasswordLength)
890         {
891             messages::propertyNotWritable(asyncResp->res, "MinPasswordLength");
892         }
893 
894         if (maxPasswordLength)
895         {
896             messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
897         }
898 
899         if (ldapObject)
900         {
901             handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP");
902         }
903 
904         if (activeDirectoryObject)
905         {
906             handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params,
907                             "ActiveDirectory");
908         }
909 
910         if (unlockTimeout)
911         {
912             crow::connections::systemBus->async_method_call(
913                 [asyncResp](const boost::system::error_code ec) {
914                     if (ec)
915                     {
916                         messages::internalError(asyncResp->res);
917                         return;
918                     }
919                     messages::success(asyncResp->res);
920                 },
921                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
922                 "org.freedesktop.DBus.Properties", "Set",
923                 "xyz.openbmc_project.User.AccountPolicy",
924                 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout));
925         }
926         if (lockoutThreshold)
927         {
928             crow::connections::systemBus->async_method_call(
929                 [asyncResp](const boost::system::error_code ec) {
930                     if (ec)
931                     {
932                         messages::internalError(asyncResp->res);
933                         return;
934                     }
935                     messages::success(asyncResp->res);
936                 },
937                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
938                 "org.freedesktop.DBus.Properties", "Set",
939                 "xyz.openbmc_project.User.AccountPolicy",
940                 "MaxLoginAttemptBeforeLockout",
941                 std::variant<uint16_t>(*lockoutThreshold));
942         }
943     }
944 };
945 
946 class AccountsCollection : public Node
947 {
948   public:
949     AccountsCollection(CrowApp& app) :
950         Node(app, "/redfish/v1/AccountService/Accounts/")
951     {
952         entityPrivileges = {
953             {boost::beast::http::verb::get,
954              {{"ConfigureUsers"}, {"ConfigureManager"}}},
955             {boost::beast::http::verb::head, {{"Login"}}},
956             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
957             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
958             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
959             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
960     }
961 
962   private:
963     void doGet(crow::Response& res, const crow::Request& req,
964                const std::vector<std::string>& params) override
965     {
966         auto asyncResp = std::make_shared<AsyncResp>(res);
967         res.jsonValue = {{"@odata.context",
968                           "/redfish/v1/"
969                           "$metadata#ManagerAccountCollection."
970                           "ManagerAccountCollection"},
971                          {"@odata.id", "/redfish/v1/AccountService/Accounts"},
972                          {"@odata.type", "#ManagerAccountCollection."
973                                          "ManagerAccountCollection"},
974                          {"Name", "Accounts Collection"},
975                          {"Description", "BMC User Accounts"}};
976 
977         crow::connections::systemBus->async_method_call(
978             [asyncResp](const boost::system::error_code ec,
979                         const ManagedObjectType& users) {
980                 if (ec)
981                 {
982                     messages::internalError(asyncResp->res);
983                     return;
984                 }
985 
986                 nlohmann::json& memberArray =
987                     asyncResp->res.jsonValue["Members"];
988                 memberArray = nlohmann::json::array();
989 
990                 asyncResp->res.jsonValue["Members@odata.count"] = users.size();
991                 for (auto& user : users)
992                 {
993                     const std::string& path =
994                         static_cast<const std::string&>(user.first);
995                     std::size_t lastIndex = path.rfind("/");
996                     if (lastIndex == std::string::npos)
997                     {
998                         lastIndex = 0;
999                     }
1000                     else
1001                     {
1002                         lastIndex += 1;
1003                     }
1004                     memberArray.push_back(
1005                         {{"@odata.id", "/redfish/v1/AccountService/Accounts/" +
1006                                            path.substr(lastIndex)}});
1007                 }
1008             },
1009             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1010             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1011     }
1012     void doPost(crow::Response& res, const crow::Request& req,
1013                 const std::vector<std::string>& params) override
1014     {
1015         auto asyncResp = std::make_shared<AsyncResp>(res);
1016 
1017         std::string username;
1018         std::string password;
1019         std::optional<std::string> roleId("User");
1020         std::optional<bool> enabled = true;
1021         if (!json_util::readJson(req, res, "UserName", username, "Password",
1022                                  password, "RoleId", roleId, "Enabled",
1023                                  enabled))
1024         {
1025             return;
1026         }
1027 
1028         std::string priv = getRoleIdFromPrivilege(*roleId);
1029         if (priv.empty())
1030         {
1031             messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
1032             return;
1033         }
1034         roleId = priv;
1035 
1036         crow::connections::systemBus->async_method_call(
1037             [asyncResp, username, password{std::move(password)}](
1038                 const boost::system::error_code ec) {
1039                 if (ec)
1040                 {
1041                     messages::resourceAlreadyExists(
1042                         asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
1043                         "UserName", username);
1044                     return;
1045                 }
1046 
1047                 if (!pamUpdatePassword(username, password))
1048                 {
1049                     // At this point we have a user that's been created, but the
1050                     // password set failed.  Something is wrong, so delete the
1051                     // user that we've already created
1052                     crow::connections::systemBus->async_method_call(
1053                         [asyncResp](const boost::system::error_code ec) {
1054                             if (ec)
1055                             {
1056                                 messages::internalError(asyncResp->res);
1057                                 return;
1058                             }
1059 
1060                             messages::invalidObject(asyncResp->res, "Password");
1061                         },
1062                         "xyz.openbmc_project.User.Manager",
1063                         "/xyz/openbmc_project/user/" + username,
1064                         "xyz.openbmc_project.Object.Delete", "Delete");
1065 
1066                     BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1067                     return;
1068                 }
1069 
1070                 messages::created(asyncResp->res);
1071                 asyncResp->res.addHeader(
1072                     "Location",
1073                     "/redfish/v1/AccountService/Accounts/" + username);
1074             },
1075             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1076             "xyz.openbmc_project.User.Manager", "CreateUser", username,
1077             std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"},
1078             *roleId, *enabled);
1079     }
1080 };
1081 
1082 class ManagerAccount : public Node
1083 {
1084   public:
1085     ManagerAccount(CrowApp& app) :
1086         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
1087     {
1088         entityPrivileges = {
1089             {boost::beast::http::verb::get,
1090              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
1091             {boost::beast::http::verb::head, {{"Login"}}},
1092             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
1093             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1094             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1095             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1096     }
1097 
1098   private:
1099     void doGet(crow::Response& res, const crow::Request& req,
1100                const std::vector<std::string>& params) override
1101     {
1102         res.jsonValue = {
1103             {"@odata.context",
1104              "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"},
1105             {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"},
1106             {"Name", "User Account"},
1107             {"Description", "User Account"},
1108             {"Password", nullptr},
1109             {"RoleId", "Administrator"}};
1110 
1111         auto asyncResp = std::make_shared<AsyncResp>(res);
1112 
1113         if (params.size() != 1)
1114         {
1115             messages::internalError(asyncResp->res);
1116             return;
1117         }
1118 
1119         crow::connections::systemBus->async_method_call(
1120             [asyncResp, accountName{std::string(params[0])}](
1121                 const boost::system::error_code ec,
1122                 const ManagedObjectType& users) {
1123                 if (ec)
1124                 {
1125                     messages::internalError(asyncResp->res);
1126                     return;
1127                 }
1128                 auto userIt = users.begin();
1129 
1130                 for (; userIt != users.end(); userIt++)
1131                 {
1132                     if (boost::ends_with(userIt->first.str, "/" + accountName))
1133                     {
1134                         break;
1135                     }
1136                 }
1137                 if (userIt == users.end())
1138                 {
1139                     messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1140                                                accountName);
1141                     return;
1142                 }
1143                 for (const auto& interface : userIt->second)
1144                 {
1145                     if (interface.first ==
1146                         "xyz.openbmc_project.User.Attributes")
1147                     {
1148                         for (const auto& property : interface.second)
1149                         {
1150                             if (property.first == "UserEnabled")
1151                             {
1152                                 const bool* userEnabled =
1153                                     std::get_if<bool>(&property.second);
1154                                 if (userEnabled == nullptr)
1155                                 {
1156                                     BMCWEB_LOG_ERROR
1157                                         << "UserEnabled wasn't a bool";
1158                                     messages::internalError(asyncResp->res);
1159                                     return;
1160                                 }
1161                                 asyncResp->res.jsonValue["Enabled"] =
1162                                     *userEnabled;
1163                             }
1164                             else if (property.first ==
1165                                      "UserLockedForFailedAttempt")
1166                             {
1167                                 const bool* userLocked =
1168                                     std::get_if<bool>(&property.second);
1169                                 if (userLocked == nullptr)
1170                                 {
1171                                     BMCWEB_LOG_ERROR << "UserLockedForF"
1172                                                         "ailedAttempt "
1173                                                         "wasn't a bool";
1174                                     messages::internalError(asyncResp->res);
1175                                     return;
1176                                 }
1177                                 asyncResp->res.jsonValue["Locked"] =
1178                                     *userLocked;
1179                                 asyncResp->res.jsonValue
1180                                     ["Locked@Redfish.AllowableValues"] = {
1181                                     "false"};
1182                             }
1183                             else if (property.first == "UserPrivilege")
1184                             {
1185                                 const std::string* userRolePtr =
1186                                     std::get_if<std::string>(&property.second);
1187                                 if (userRolePtr == nullptr)
1188                                 {
1189                                     BMCWEB_LOG_ERROR
1190                                         << "UserPrivilege wasn't a "
1191                                            "string";
1192                                     messages::internalError(asyncResp->res);
1193                                     return;
1194                                 }
1195                                 std::string priv =
1196                                     getPrivilegeFromRoleId(*userRolePtr);
1197                                 if (priv.empty())
1198                                 {
1199                                     BMCWEB_LOG_ERROR << "Invalid user role";
1200                                     messages::internalError(asyncResp->res);
1201                                     return;
1202                                 }
1203                                 asyncResp->res.jsonValue["RoleId"] = priv;
1204 
1205                                 asyncResp->res.jsonValue["Links"]["Role"] = {
1206                                     {"@odata.id", "/redfish/v1/AccountService/"
1207                                                   "Roles/" +
1208                                                       priv}};
1209                             }
1210                         }
1211                     }
1212                 }
1213 
1214                 asyncResp->res.jsonValue["@odata.id"] =
1215                     "/redfish/v1/AccountService/Accounts/" + accountName;
1216                 asyncResp->res.jsonValue["Id"] = accountName;
1217                 asyncResp->res.jsonValue["UserName"] = accountName;
1218             },
1219             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1220             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1221     }
1222 
1223     void doPatch(crow::Response& res, const crow::Request& req,
1224                  const std::vector<std::string>& params) override
1225     {
1226         auto asyncResp = std::make_shared<AsyncResp>(res);
1227         if (params.size() != 1)
1228         {
1229             messages::internalError(asyncResp->res);
1230             return;
1231         }
1232 
1233         std::optional<std::string> newUserName;
1234         std::optional<std::string> password;
1235         std::optional<bool> enabled;
1236         std::optional<std::string> roleId;
1237         std::optional<bool> locked;
1238         if (!json_util::readJson(req, res, "UserName", newUserName, "Password",
1239                                  password, "RoleId", roleId, "Enabled", enabled,
1240                                  "Locked", locked))
1241         {
1242             return;
1243         }
1244 
1245         const std::string& username = params[0];
1246 
1247         if (!newUserName)
1248         {
1249             // If the username isn't being updated, we can update the properties
1250             // directly
1251             updateUserProperties(asyncResp, username, password, enabled, roleId,
1252                                  locked);
1253             return;
1254         }
1255         else
1256         {
1257             crow::connections::systemBus->async_method_call(
1258                 [this, asyncResp, username, password(std::move(password)),
1259                  roleId(std::move(roleId)), enabled(std::move(enabled)),
1260                  newUser{std::string(*newUserName)}, locked(std::move(locked))](
1261                     const boost::system::error_code ec) {
1262                     if (ec)
1263                     {
1264                         BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1265                         messages::resourceNotFound(
1266                             asyncResp->res,
1267                             "#ManagerAccount.v1_0_3.ManagerAccount", username);
1268                         return;
1269                     }
1270 
1271                     updateUserProperties(asyncResp, newUser, password, enabled,
1272                                          roleId, locked);
1273                 },
1274                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1275                 "xyz.openbmc_project.User.Manager", "RenameUser", username,
1276                 *newUserName);
1277         }
1278     }
1279 
1280     void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp,
1281                               const std::string& username,
1282                               std::optional<std::string> password,
1283                               std::optional<bool> enabled,
1284                               std::optional<std::string> roleId,
1285                               std::optional<bool> locked)
1286     {
1287         if (password)
1288         {
1289             if (!pamUpdatePassword(username, *password))
1290             {
1291                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1292                 messages::internalError(asyncResp->res);
1293                 return;
1294             }
1295         }
1296 
1297         std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username;
1298         dbus::utility::escapePathForDbus(dbusObjectPath);
1299 
1300         dbus::utility::checkDbusPathExists(
1301             dbusObjectPath,
1302             [dbusObjectPath(std::move(dbusObjectPath)), username,
1303              password(std::move(password)), roleId(std::move(roleId)),
1304              enabled(std::move(enabled)), locked(std::move(locked)),
1305              asyncResp{std::move(asyncResp)}](int rc) {
1306                 if (!rc)
1307                 {
1308                     messages::invalidObject(asyncResp->res, username.c_str());
1309                     return;
1310                 }
1311                 if (enabled)
1312                 {
1313                     crow::connections::systemBus->async_method_call(
1314                         [asyncResp](const boost::system::error_code ec) {
1315                             if (ec)
1316                             {
1317                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1318                                                  << ec;
1319                                 messages::internalError(asyncResp->res);
1320                                 return;
1321                             }
1322                             messages::success(asyncResp->res);
1323                             return;
1324                         },
1325                         "xyz.openbmc_project.User.Manager",
1326                         dbusObjectPath.c_str(),
1327                         "org.freedesktop.DBus.Properties", "Set",
1328                         "xyz.openbmc_project.User.Attributes", "UserEnabled",
1329                         std::variant<bool>{*enabled});
1330                 }
1331 
1332                 if (roleId)
1333                 {
1334                     std::string priv = getRoleIdFromPrivilege(*roleId);
1335                     if (priv.empty())
1336                     {
1337                         messages::propertyValueNotInList(asyncResp->res,
1338                                                          *roleId, "RoleId");
1339                         return;
1340                     }
1341 
1342                     crow::connections::systemBus->async_method_call(
1343                         [asyncResp](const boost::system::error_code ec) {
1344                             if (ec)
1345                             {
1346                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1347                                                  << ec;
1348                                 messages::internalError(asyncResp->res);
1349                                 return;
1350                             }
1351                             messages::success(asyncResp->res);
1352                         },
1353                         "xyz.openbmc_project.User.Manager",
1354                         dbusObjectPath.c_str(),
1355                         "org.freedesktop.DBus.Properties", "Set",
1356                         "xyz.openbmc_project.User.Attributes", "UserPrivilege",
1357                         std::variant<std::string>{priv});
1358                 }
1359 
1360                 if (locked)
1361                 {
1362                     // admin can unlock the account which is locked by
1363                     // successive authentication failures but admin should not
1364                     // be allowed to lock an account.
1365                     if (*locked)
1366                     {
1367                         messages::propertyValueNotInList(asyncResp->res, "true",
1368                                                          "Locked");
1369                         return;
1370                     }
1371 
1372                     crow::connections::systemBus->async_method_call(
1373                         [asyncResp](const boost::system::error_code ec) {
1374                             if (ec)
1375                             {
1376                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1377                                                  << ec;
1378                                 messages::internalError(asyncResp->res);
1379                                 return;
1380                             }
1381                             messages::success(asyncResp->res);
1382                             return;
1383                         },
1384                         "xyz.openbmc_project.User.Manager",
1385                         dbusObjectPath.c_str(),
1386                         "org.freedesktop.DBus.Properties", "Set",
1387                         "xyz.openbmc_project.User.Attributes",
1388                         "UserLockedForFailedAttempt",
1389                         sdbusplus::message::variant<bool>{*locked});
1390                 }
1391             });
1392     }
1393 
1394     void doDelete(crow::Response& res, const crow::Request& req,
1395                   const std::vector<std::string>& params) override
1396     {
1397         auto asyncResp = std::make_shared<AsyncResp>(res);
1398 
1399         if (params.size() != 1)
1400         {
1401             messages::internalError(asyncResp->res);
1402             return;
1403         }
1404 
1405         const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
1406 
1407         crow::connections::systemBus->async_method_call(
1408             [asyncResp, username{std::move(params[0])}](
1409                 const boost::system::error_code ec) {
1410                 if (ec)
1411                 {
1412                     messages::resourceNotFound(
1413                         asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount",
1414                         username);
1415                     return;
1416                 }
1417 
1418                 messages::accountRemoved(asyncResp->res);
1419             },
1420             "xyz.openbmc_project.User.Manager", userPath,
1421             "xyz.openbmc_project.Object.Delete", "Delete");
1422     }
1423 };
1424 
1425 } // namespace redfish
1426