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