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