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 <persistent_data.hpp>
23 #include <utils/json_utils.hpp>
24 
25 #include <variant>
26 
27 namespace redfish
28 {
29 
30 constexpr const char* ldapConfigObjectName =
31     "/xyz/openbmc_project/user/ldap/openldap";
32 constexpr const char* adConfigObject =
33     "/xyz/openbmc_project/user/ldap/active_directory";
34 
35 constexpr const char* ldapRootObject = "/xyz/openbmc_project/user/ldap";
36 constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config";
37 constexpr const char* ldapConfigInterface =
38     "xyz.openbmc_project.User.Ldap.Config";
39 constexpr const char* ldapCreateInterface =
40     "xyz.openbmc_project.User.Ldap.Create";
41 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable";
42 constexpr const char* ldapPrivMapperInterface =
43     "xyz.openbmc_project.User.PrivilegeMapper";
44 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
45 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties";
46 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper";
47 constexpr const char* mapperObjectPath = "/xyz/openbmc_project/object_mapper";
48 constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper";
49 
50 struct LDAPRoleMapData
51 {
52     std::string groupName;
53     std::string privilege;
54 };
55 
56 struct LDAPConfigData
57 {
58     std::string uri{};
59     std::string bindDN{};
60     std::string baseDN{};
61     std::string searchScope{};
62     std::string serverType{};
63     bool serviceEnabled = false;
64     std::string userNameAttribute{};
65     std::string groupAttribute{};
66     std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList;
67 };
68 
69 using DbusVariantType = std::variant<bool, int32_t, std::string>;
70 
71 using DbusInterfaceType = boost::container::flat_map<
72     std::string, boost::container::flat_map<std::string, DbusVariantType>>;
73 
74 using ManagedObjectType =
75     std::vector<std::pair<sdbusplus::message::object_path, DbusInterfaceType>>;
76 
77 using GetObjectType =
78     std::vector<std::pair<std::string, std::vector<std::string>>>;
79 
80 inline std::string getRoleIdFromPrivilege(std::string_view role)
81 {
82     if (role == "priv-admin")
83     {
84         return "Administrator";
85     }
86     if (role == "priv-user")
87     {
88         return "ReadOnly";
89     }
90     if (role == "priv-operator")
91     {
92         return "Operator";
93     }
94     if ((role == "") || (role == "priv-noaccess"))
95     {
96         return "NoAccess";
97     }
98     return "";
99 }
100 inline std::string getPrivilegeFromRoleId(std::string_view role)
101 {
102     if (role == "Administrator")
103     {
104         return "priv-admin";
105     }
106     if (role == "ReadOnly")
107     {
108         return "priv-user";
109     }
110     if (role == "Operator")
111     {
112         return "priv-operator";
113     }
114     if ((role == "NoAccess") || (role == ""))
115     {
116         return "priv-noaccess";
117     }
118     return "";
119 }
120 
121 inline void userErrorMessageHandler(const sd_bus_error* e,
122                                     const std::shared_ptr<AsyncResp>& asyncResp,
123                                     const std::string& newUser,
124                                     const std::string& username)
125 {
126     if (e == nullptr)
127     {
128         messages::internalError(asyncResp->res);
129         return;
130     }
131 
132     const char* errorMessage = e->name;
133     if (strcmp(errorMessage,
134                "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0)
135     {
136         messages::resourceAlreadyExists(asyncResp->res,
137                                         "#ManagerAccount.v1_4_0.ManagerAccount",
138                                         "UserName", newUser);
139     }
140     else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error."
141                                   "UserNameDoesNotExist") == 0)
142     {
143         messages::resourceNotFound(
144             asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", username);
145     }
146     else if ((strcmp(errorMessage,
147                      "xyz.openbmc_project.Common.Error.InvalidArgument") ==
148               0) ||
149              (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error."
150                                    "UserNameGroupFail") == 0))
151     {
152         messages::propertyValueFormatError(asyncResp->res, newUser, "UserName");
153     }
154     else if (strcmp(errorMessage,
155                     "xyz.openbmc_project.User.Common.Error.NoResource") == 0)
156     {
157         messages::createLimitReachedForResource(asyncResp->res);
158     }
159     else
160     {
161         messages::internalError(asyncResp->res);
162     }
163 
164     return;
165 }
166 
167 inline void parseLDAPConfigData(nlohmann::json& jsonResponse,
168                                 const LDAPConfigData& confData,
169                                 const std::string& ldapType)
170 {
171     std::string service =
172         (ldapType == "LDAP") ? "LDAPService" : "ActiveDirectoryService";
173     nlohmann::json ldap = {
174         {"ServiceEnabled", confData.serviceEnabled},
175         {"ServiceAddresses", nlohmann::json::array({confData.uri})},
176         {"Authentication",
177          {{"AuthenticationType", "UsernameAndPassword"},
178           {"Username", confData.bindDN},
179           {"Password", nullptr}}},
180         {"LDAPService",
181          {{"SearchSettings",
182            {{"BaseDistinguishedNames",
183              nlohmann::json::array({confData.baseDN})},
184             {"UsernameAttribute", confData.userNameAttribute},
185             {"GroupsAttribute", confData.groupAttribute}}}}},
186     };
187 
188     jsonResponse[ldapType].update(ldap);
189 
190     nlohmann::json& roleMapArray = jsonResponse[ldapType]["RemoteRoleMapping"];
191     roleMapArray = nlohmann::json::array();
192     for (auto& obj : confData.groupRoleList)
193     {
194         BMCWEB_LOG_DEBUG << "Pushing the data groupName="
195                          << obj.second.groupName << "\n";
196         roleMapArray.push_back(
197             {nlohmann::json::array({"RemoteGroup", obj.second.groupName}),
198              nlohmann::json::array(
199                  {"LocalRole", getRoleIdFromPrivilege(obj.second.privilege)})});
200     }
201 }
202 
203 /**
204  *  @brief validates given JSON input and then calls appropriate method to
205  * create, to delete or to set Rolemapping object based on the given input.
206  *
207  */
208 inline void handleRoleMapPatch(
209     const std::shared_ptr<AsyncResp>& asyncResp,
210     const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData,
211     const std::string& serverType, const std::vector<nlohmann::json>& input)
212 {
213     for (size_t index = 0; index < input.size(); index++)
214     {
215         const nlohmann::json& thisJson = input[index];
216 
217         if (thisJson.is_null())
218         {
219             // delete the existing object
220             if (index < roleMapObjData.size())
221             {
222                 crow::connections::systemBus->async_method_call(
223                     [asyncResp, roleMapObjData, serverType,
224                      index](const boost::system::error_code ec) {
225                         if (ec)
226                         {
227                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
228                             messages::internalError(asyncResp->res);
229                             return;
230                         }
231                         asyncResp->res
232                             .jsonValue[serverType]["RemoteRoleMapping"][index] =
233                             nullptr;
234                     },
235                     ldapDbusService, roleMapObjData[index].first,
236                     "xyz.openbmc_project.Object.Delete", "Delete");
237             }
238             else
239             {
240                 BMCWEB_LOG_ERROR << "Can't delete the object";
241                 messages::propertyValueTypeError(
242                     asyncResp->res, thisJson.dump(),
243                     "RemoteRoleMapping/" + std::to_string(index));
244                 return;
245             }
246         }
247         else if (thisJson.empty())
248         {
249             // Don't do anything for the empty objects,parse next json
250             // eg {"RemoteRoleMapping",[{}]}
251         }
252         else
253         {
254             // update/create the object
255             std::optional<std::string> remoteGroup;
256             std::optional<std::string> localRole;
257 
258             // This is a copy, but it's required in this case because of how
259             // readJson is structured
260             nlohmann::json thisJsonCopy = thisJson;
261             if (!json_util::readJson(thisJsonCopy, asyncResp->res,
262                                      "RemoteGroup", remoteGroup, "LocalRole",
263                                      localRole))
264             {
265                 continue;
266             }
267 
268             // Update existing RoleMapping Object
269             if (index < roleMapObjData.size())
270             {
271                 BMCWEB_LOG_DEBUG << "Update Role Map Object";
272                 // If "RemoteGroup" info is provided
273                 if (remoteGroup)
274                 {
275                     crow::connections::systemBus->async_method_call(
276                         [asyncResp, roleMapObjData, serverType, index,
277                          remoteGroup](const boost::system::error_code ec) {
278                             if (ec)
279                             {
280                                 BMCWEB_LOG_ERROR << "DBUS response error: "
281                                                  << ec;
282                                 messages::internalError(asyncResp->res);
283                                 return;
284                             }
285                             asyncResp->res
286                                 .jsonValue[serverType]["RemoteRoleMapping"]
287                                           [index]["RemoteGroup"] = *remoteGroup;
288                         },
289                         ldapDbusService, roleMapObjData[index].first,
290                         propertyInterface, "Set",
291                         "xyz.openbmc_project.User.PrivilegeMapperEntry",
292                         "GroupName",
293                         std::variant<std::string>(std::move(*remoteGroup)));
294                 }
295 
296                 // If "LocalRole" info is provided
297                 if (localRole)
298                 {
299                     crow::connections::systemBus->async_method_call(
300                         [asyncResp, roleMapObjData, serverType, index,
301                          localRole](const boost::system::error_code ec) {
302                             if (ec)
303                             {
304                                 BMCWEB_LOG_ERROR << "DBUS response error: "
305                                                  << ec;
306                                 messages::internalError(asyncResp->res);
307                                 return;
308                             }
309                             asyncResp->res
310                                 .jsonValue[serverType]["RemoteRoleMapping"]
311                                           [index]["LocalRole"] = *localRole;
312                         },
313                         ldapDbusService, roleMapObjData[index].first,
314                         propertyInterface, "Set",
315                         "xyz.openbmc_project.User.PrivilegeMapperEntry",
316                         "Privilege",
317                         std::variant<std::string>(
318                             getPrivilegeFromRoleId(std::move(*localRole))));
319                 }
320             }
321             // Create a new RoleMapping Object.
322             else
323             {
324                 BMCWEB_LOG_DEBUG
325                     << "setRoleMappingProperties: Creating new Object";
326                 std::string pathString =
327                     "RemoteRoleMapping/" + std::to_string(index);
328 
329                 if (!localRole)
330                 {
331                     messages::propertyMissing(asyncResp->res,
332                                               pathString + "/LocalRole");
333                     continue;
334                 }
335                 if (!remoteGroup)
336                 {
337                     messages::propertyMissing(asyncResp->res,
338                                               pathString + "/RemoteGroup");
339                     continue;
340                 }
341 
342                 std::string dbusObjectPath;
343                 if (serverType == "ActiveDirectory")
344                 {
345                     dbusObjectPath = adConfigObject;
346                 }
347                 else if (serverType == "LDAP")
348                 {
349                     dbusObjectPath = ldapConfigObjectName;
350                 }
351 
352                 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup
353                                  << ",LocalRole=" << *localRole;
354 
355                 crow::connections::systemBus->async_method_call(
356                     [asyncResp, serverType, localRole,
357                      remoteGroup](const boost::system::error_code ec) {
358                         if (ec)
359                         {
360                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
361                             messages::internalError(asyncResp->res);
362                             return;
363                         }
364                         nlohmann::json& remoteRoleJson =
365                             asyncResp->res
366                                 .jsonValue[serverType]["RemoteRoleMapping"];
367                         remoteRoleJson.push_back(
368                             {{"LocalRole", *localRole},
369                              {"RemoteGroup", *remoteGroup}});
370                     },
371                     ldapDbusService, dbusObjectPath, ldapPrivMapperInterface,
372                     "Create", *remoteGroup,
373                     getPrivilegeFromRoleId(std::move(*localRole)));
374             }
375         }
376     }
377 }
378 
379 /**
380  * Function that retrieves all properties for LDAP config object
381  * into JSON
382  */
383 template <typename CallbackFunc>
384 inline void getLDAPConfigData(const std::string& ldapType,
385                               CallbackFunc&& callback)
386 {
387 
388     const std::array<const char*, 2> interfaces = {ldapEnableInterface,
389                                                    ldapConfigInterface};
390 
391     crow::connections::systemBus->async_method_call(
392         [callback, ldapType](const boost::system::error_code ec,
393                              const GetObjectType& resp) {
394             if (ec || resp.empty())
395             {
396                 BMCWEB_LOG_ERROR << "DBUS response error during getting of "
397                                     "service name: "
398                                  << ec;
399                 LDAPConfigData empty{};
400                 callback(false, empty, ldapType);
401                 return;
402             }
403             std::string service = resp.begin()->first;
404             crow::connections::systemBus->async_method_call(
405                 [callback, ldapType](const boost::system::error_code errorCode,
406                                      const ManagedObjectType& ldapObjects) {
407                     LDAPConfigData confData{};
408                     if (errorCode)
409                     {
410                         callback(false, confData, ldapType);
411                         BMCWEB_LOG_ERROR << "D-Bus responses error: "
412                                          << errorCode;
413                         return;
414                     }
415 
416                     std::string ldapDbusType;
417                     std::string searchString;
418 
419                     if (ldapType == "LDAP")
420                     {
421                         ldapDbusType = "xyz.openbmc_project.User.Ldap.Config."
422                                        "Type.OpenLdap";
423                         searchString = "openldap";
424                     }
425                     else if (ldapType == "ActiveDirectory")
426                     {
427                         ldapDbusType =
428                             "xyz.openbmc_project.User.Ldap.Config.Type."
429                             "ActiveDirectory";
430                         searchString = "active_directory";
431                     }
432                     else
433                     {
434                         BMCWEB_LOG_ERROR
435                             << "Can't get the DbusType for the given type="
436                             << ldapType;
437                         callback(false, confData, ldapType);
438                         return;
439                     }
440 
441                     std::string ldapEnableInterfaceStr = ldapEnableInterface;
442                     std::string ldapConfigInterfaceStr = ldapConfigInterface;
443 
444                     for (const auto& object : ldapObjects)
445                     {
446                         // let's find the object whose ldap type is equal to the
447                         // given type
448                         if (object.first.str.find(searchString) ==
449                             std::string::npos)
450                         {
451                             continue;
452                         }
453 
454                         for (const auto& interface : object.second)
455                         {
456                             if (interface.first == ldapEnableInterfaceStr)
457                             {
458                                 // rest of the properties are string.
459                                 for (const auto& property : interface.second)
460                                 {
461                                     if (property.first == "Enabled")
462                                     {
463                                         const bool* value =
464                                             std::get_if<bool>(&property.second);
465                                         if (value == nullptr)
466                                         {
467                                             continue;
468                                         }
469                                         confData.serviceEnabled = *value;
470                                         break;
471                                     }
472                                 }
473                             }
474                             else if (interface.first == ldapConfigInterfaceStr)
475                             {
476 
477                                 for (const auto& property : interface.second)
478                                 {
479                                     const std::string* strValue =
480                                         std::get_if<std::string>(
481                                             &property.second);
482                                     if (strValue == nullptr)
483                                     {
484                                         continue;
485                                     }
486                                     if (property.first == "LDAPServerURI")
487                                     {
488                                         confData.uri = *strValue;
489                                     }
490                                     else if (property.first == "LDAPBindDN")
491                                     {
492                                         confData.bindDN = *strValue;
493                                     }
494                                     else if (property.first == "LDAPBaseDN")
495                                     {
496                                         confData.baseDN = *strValue;
497                                     }
498                                     else if (property.first ==
499                                              "LDAPSearchScope")
500                                     {
501                                         confData.searchScope = *strValue;
502                                     }
503                                     else if (property.first ==
504                                              "GroupNameAttribute")
505                                     {
506                                         confData.groupAttribute = *strValue;
507                                     }
508                                     else if (property.first ==
509                                              "UserNameAttribute")
510                                     {
511                                         confData.userNameAttribute = *strValue;
512                                     }
513                                     else if (property.first == "LDAPType")
514                                     {
515                                         confData.serverType = *strValue;
516                                     }
517                                 }
518                             }
519                             else if (interface.first ==
520                                      "xyz.openbmc_project.User."
521                                      "PrivilegeMapperEntry")
522                             {
523                                 LDAPRoleMapData roleMapData{};
524                                 for (const auto& property : interface.second)
525                                 {
526                                     const std::string* strValue =
527                                         std::get_if<std::string>(
528                                             &property.second);
529 
530                                     if (strValue == nullptr)
531                                     {
532                                         continue;
533                                     }
534 
535                                     if (property.first == "GroupName")
536                                     {
537                                         roleMapData.groupName = *strValue;
538                                     }
539                                     else if (property.first == "Privilege")
540                                     {
541                                         roleMapData.privilege = *strValue;
542                                     }
543                                 }
544 
545                                 confData.groupRoleList.emplace_back(
546                                     object.first.str, roleMapData);
547                             }
548                         }
549                     }
550                     callback(true, confData, ldapType);
551                 },
552                 service, ldapRootObject, dbusObjManagerIntf,
553                 "GetManagedObjects");
554         },
555         mapperBusName, mapperObjectPath, mapperIntf, "GetObject",
556         ldapConfigObjectName, interfaces);
557 }
558 
559 class AccountService : public Node
560 {
561   public:
562     AccountService(App& app) : Node(app, "/redfish/v1/AccountService/")
563     {
564         entityPrivileges = {
565             {boost::beast::http::verb::get, {{"Login"}}},
566             {boost::beast::http::verb::head, {{"Login"}}},
567             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
568             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
569             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
570             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
571     }
572 
573   private:
574     /**
575      * @brief parses the authentication section under the LDAP
576      * @param input JSON data
577      * @param asyncResp pointer to the JSON response
578      * @param userName  userName to be filled from the given JSON.
579      * @param password  password to be filled from the given JSON.
580      */
581     void
582         parseLDAPAuthenticationJson(nlohmann::json input,
583                                     const std::shared_ptr<AsyncResp>& asyncResp,
584                                     std::optional<std::string>& username,
585                                     std::optional<std::string>& password)
586     {
587         std::optional<std::string> authType;
588 
589         if (!json_util::readJson(input, asyncResp->res, "AuthenticationType",
590                                  authType, "Username", username, "Password",
591                                  password))
592         {
593             return;
594         }
595         if (!authType)
596         {
597             return;
598         }
599         if (*authType != "UsernameAndPassword")
600         {
601             messages::propertyValueNotInList(asyncResp->res, *authType,
602                                              "AuthenticationType");
603             return;
604         }
605     }
606     /**
607      * @brief parses the LDAPService section under the LDAP
608      * @param input JSON data
609      * @param asyncResp pointer to the JSON response
610      * @param baseDNList baseDN to be filled from the given JSON.
611      * @param userNameAttribute  userName to be filled from the given JSON.
612      * @param groupaAttribute  password to be filled from the given JSON.
613      */
614 
615     void parseLDAPServiceJson(
616         nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp,
617         std::optional<std::vector<std::string>>& baseDNList,
618         std::optional<std::string>& userNameAttribute,
619         std::optional<std::string>& groupsAttribute)
620     {
621         std::optional<nlohmann::json> searchSettings;
622 
623         if (!json_util::readJson(input, asyncResp->res, "SearchSettings",
624                                  searchSettings))
625         {
626             return;
627         }
628         if (!searchSettings)
629         {
630             return;
631         }
632         if (!json_util::readJson(*searchSettings, asyncResp->res,
633                                  "BaseDistinguishedNames", baseDNList,
634                                  "UsernameAttribute", userNameAttribute,
635                                  "GroupsAttribute", groupsAttribute))
636         {
637             return;
638         }
639     }
640     /**
641      * @brief updates the LDAP server address and updates the
642               json response with the new value.
643      * @param serviceAddressList address to be updated.
644      * @param asyncResp pointer to the JSON response
645      * @param ldapServerElementName Type of LDAP
646      server(openLDAP/ActiveDirectory)
647      */
648 
649     void handleServiceAddressPatch(
650         const std::vector<std::string>& serviceAddressList,
651         const std::shared_ptr<AsyncResp>& asyncResp,
652         const std::string& ldapServerElementName,
653         const std::string& ldapConfigObject)
654     {
655         crow::connections::systemBus->async_method_call(
656             [asyncResp, ldapServerElementName,
657              serviceAddressList](const boost::system::error_code ec) {
658                 if (ec)
659                 {
660                     BMCWEB_LOG_DEBUG
661                         << "Error Occurred in updating the service address";
662                     messages::internalError(asyncResp->res);
663                     return;
664                 }
665                 std::vector<std::string> modifiedserviceAddressList = {
666                     serviceAddressList.front()};
667                 asyncResp->res
668                     .jsonValue[ldapServerElementName]["ServiceAddresses"] =
669                     modifiedserviceAddressList;
670                 if ((serviceAddressList).size() > 1)
671                 {
672                     messages::propertyValueModified(asyncResp->res,
673                                                     "ServiceAddresses",
674                                                     serviceAddressList.front());
675                 }
676                 BMCWEB_LOG_DEBUG << "Updated the service address";
677             },
678             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
679             ldapConfigInterface, "LDAPServerURI",
680             std::variant<std::string>(serviceAddressList.front()));
681     }
682     /**
683      * @brief updates the LDAP Bind DN and updates the
684               json response with the new value.
685      * @param username name of the user which needs to be updated.
686      * @param asyncResp pointer to the JSON response
687      * @param ldapServerElementName Type of LDAP
688      server(openLDAP/ActiveDirectory)
689      */
690 
691     void handleUserNamePatch(const std::string& username,
692                              const std::shared_ptr<AsyncResp>& asyncResp,
693                              const std::string& ldapServerElementName,
694                              const std::string& ldapConfigObject)
695     {
696         crow::connections::systemBus->async_method_call(
697             [asyncResp, username,
698              ldapServerElementName](const boost::system::error_code ec) {
699                 if (ec)
700                 {
701                     BMCWEB_LOG_DEBUG
702                         << "Error occurred in updating the username";
703                     messages::internalError(asyncResp->res);
704                     return;
705                 }
706                 asyncResp->res.jsonValue[ldapServerElementName]
707                                         ["Authentication"]["Username"] =
708                     username;
709                 BMCWEB_LOG_DEBUG << "Updated the username";
710             },
711             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
712             ldapConfigInterface, "LDAPBindDN",
713             std::variant<std::string>(username));
714     }
715 
716     /**
717      * @brief updates the LDAP password
718      * @param password : ldap password which needs to be updated.
719      * @param asyncResp pointer to the JSON response
720      * @param ldapServerElementName Type of LDAP
721      *        server(openLDAP/ActiveDirectory)
722      */
723 
724     void handlePasswordPatch(const std::string& password,
725                              const std::shared_ptr<AsyncResp>& asyncResp,
726                              const std::string& ldapServerElementName,
727                              const std::string& ldapConfigObject)
728     {
729         crow::connections::systemBus->async_method_call(
730             [asyncResp, password,
731              ldapServerElementName](const boost::system::error_code ec) {
732                 if (ec)
733                 {
734                     BMCWEB_LOG_DEBUG
735                         << "Error occurred in updating the password";
736                     messages::internalError(asyncResp->res);
737                     return;
738                 }
739                 asyncResp->res.jsonValue[ldapServerElementName]
740                                         ["Authentication"]["Password"] = "";
741                 BMCWEB_LOG_DEBUG << "Updated the password";
742             },
743             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
744             ldapConfigInterface, "LDAPBindDNPassword",
745             std::variant<std::string>(password));
746     }
747 
748     /**
749      * @brief updates the LDAP BaseDN and updates the
750               json response with the new value.
751      * @param baseDNList baseDN list which needs to be updated.
752      * @param asyncResp pointer to the JSON response
753      * @param ldapServerElementName Type of LDAP
754      server(openLDAP/ActiveDirectory)
755      */
756 
757     void handleBaseDNPatch(const std::vector<std::string>& baseDNList,
758                            const std::shared_ptr<AsyncResp>& asyncResp,
759                            const std::string& ldapServerElementName,
760                            const std::string& ldapConfigObject)
761     {
762         crow::connections::systemBus->async_method_call(
763             [asyncResp, baseDNList,
764              ldapServerElementName](const boost::system::error_code ec) {
765                 if (ec)
766                 {
767                     BMCWEB_LOG_DEBUG
768                         << "Error Occurred in Updating the base DN";
769                     messages::internalError(asyncResp->res);
770                     return;
771                 }
772                 auto& serverTypeJson =
773                     asyncResp->res.jsonValue[ldapServerElementName];
774                 auto& searchSettingsJson =
775                     serverTypeJson["LDAPService"]["SearchSettings"];
776                 std::vector<std::string> modifiedBaseDNList = {
777                     baseDNList.front()};
778                 searchSettingsJson["BaseDistinguishedNames"] =
779                     modifiedBaseDNList;
780                 if (baseDNList.size() > 1)
781                 {
782                     messages::propertyValueModified(asyncResp->res,
783                                                     "BaseDistinguishedNames",
784                                                     baseDNList.front());
785                 }
786                 BMCWEB_LOG_DEBUG << "Updated the base DN";
787             },
788             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
789             ldapConfigInterface, "LDAPBaseDN",
790             std::variant<std::string>(baseDNList.front()));
791     }
792     /**
793      * @brief updates the LDAP user name attribute and updates the
794               json response with the new value.
795      * @param userNameAttribute attribute to be updated.
796      * @param asyncResp pointer to the JSON response
797      * @param ldapServerElementName Type of LDAP
798      server(openLDAP/ActiveDirectory)
799      */
800 
801     void handleUserNameAttrPatch(const std::string& userNameAttribute,
802                                  const std::shared_ptr<AsyncResp>& asyncResp,
803                                  const std::string& ldapServerElementName,
804                                  const std::string& ldapConfigObject)
805     {
806         crow::connections::systemBus->async_method_call(
807             [asyncResp, userNameAttribute,
808              ldapServerElementName](const boost::system::error_code ec) {
809                 if (ec)
810                 {
811                     BMCWEB_LOG_DEBUG << "Error Occurred in Updating the "
812                                         "username attribute";
813                     messages::internalError(asyncResp->res);
814                     return;
815                 }
816                 auto& serverTypeJson =
817                     asyncResp->res.jsonValue[ldapServerElementName];
818                 auto& searchSettingsJson =
819                     serverTypeJson["LDAPService"]["SearchSettings"];
820                 searchSettingsJson["UsernameAttribute"] = userNameAttribute;
821                 BMCWEB_LOG_DEBUG << "Updated the user name attr.";
822             },
823             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
824             ldapConfigInterface, "UserNameAttribute",
825             std::variant<std::string>(userNameAttribute));
826     }
827     /**
828      * @brief updates the LDAP group attribute and updates the
829               json response with the new value.
830      * @param groupsAttribute attribute to be updated.
831      * @param asyncResp pointer to the JSON response
832      * @param ldapServerElementName Type of LDAP
833      server(openLDAP/ActiveDirectory)
834      */
835 
836     void handleGroupNameAttrPatch(const std::string& groupsAttribute,
837                                   const std::shared_ptr<AsyncResp>& asyncResp,
838                                   const std::string& ldapServerElementName,
839                                   const std::string& ldapConfigObject)
840     {
841         crow::connections::systemBus->async_method_call(
842             [asyncResp, groupsAttribute,
843              ldapServerElementName](const boost::system::error_code ec) {
844                 if (ec)
845                 {
846                     BMCWEB_LOG_DEBUG << "Error Occurred in Updating the "
847                                         "groupname attribute";
848                     messages::internalError(asyncResp->res);
849                     return;
850                 }
851                 auto& serverTypeJson =
852                     asyncResp->res.jsonValue[ldapServerElementName];
853                 auto& searchSettingsJson =
854                     serverTypeJson["LDAPService"]["SearchSettings"];
855                 searchSettingsJson["GroupsAttribute"] = groupsAttribute;
856                 BMCWEB_LOG_DEBUG << "Updated the groupname attr";
857             },
858             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
859             ldapConfigInterface, "GroupNameAttribute",
860             std::variant<std::string>(groupsAttribute));
861     }
862     /**
863      * @brief updates the LDAP service enable and updates the
864               json response with the new value.
865      * @param input JSON data.
866      * @param asyncResp pointer to the JSON response
867      * @param ldapServerElementName Type of LDAP
868      server(openLDAP/ActiveDirectory)
869      */
870 
871     void handleServiceEnablePatch(bool serviceEnabled,
872                                   const std::shared_ptr<AsyncResp>& asyncResp,
873                                   const std::string& ldapServerElementName,
874                                   const std::string& ldapConfigObject)
875     {
876         crow::connections::systemBus->async_method_call(
877             [asyncResp, serviceEnabled,
878              ldapServerElementName](const boost::system::error_code ec) {
879                 if (ec)
880                 {
881                     BMCWEB_LOG_DEBUG
882                         << "Error Occurred in Updating the service enable";
883                     messages::internalError(asyncResp->res);
884                     return;
885                 }
886                 asyncResp->res
887                     .jsonValue[ldapServerElementName]["ServiceEnabled"] =
888                     serviceEnabled;
889                 BMCWEB_LOG_DEBUG << "Updated Service enable = "
890                                  << serviceEnabled;
891             },
892             ldapDbusService, ldapConfigObject, propertyInterface, "Set",
893             ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled));
894     }
895 
896     void handleAuthMethodsPatch(nlohmann::json& input,
897                                 const std::shared_ptr<AsyncResp>& asyncResp)
898     {
899         std::optional<bool> basicAuth;
900         std::optional<bool> cookie;
901         std::optional<bool> sessionToken;
902         std::optional<bool> xToken;
903         std::optional<bool> tls;
904 
905         if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth,
906                                  "Cookie", cookie, "SessionToken", sessionToken,
907                                  "XToken", xToken, "TLS", tls))
908         {
909             BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag";
910             return;
911         }
912 
913         // Make a copy of methods configuration
914         persistent_data::AuthConfigMethods authMethodsConfig =
915             persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
916 
917         if (basicAuth)
918         {
919 #ifndef BMCWEB_ENABLE_BASIC_AUTHENTICATION
920             messages::actionNotSupported(
921                 asyncResp->res, "Setting BasicAuth when basic-auth feature "
922                                 "is disabled");
923             return;
924 #endif
925             authMethodsConfig.basic = *basicAuth;
926         }
927 
928         if (cookie)
929         {
930 #ifndef BMCWEB_ENABLE_COOKIE_AUTHENTICATION
931             messages::actionNotSupported(
932                 asyncResp->res, "Setting Cookie when cookie-auth feature "
933                                 "is disabled");
934             return;
935 #endif
936             authMethodsConfig.cookie = *cookie;
937         }
938 
939         if (sessionToken)
940         {
941 #ifndef BMCWEB_ENABLE_SESSION_AUTHENTICATION
942             messages::actionNotSupported(
943                 asyncResp->res,
944                 "Setting SessionToken when session-auth feature "
945                 "is disabled");
946             return;
947 #endif
948             authMethodsConfig.sessionToken = *sessionToken;
949         }
950 
951         if (xToken)
952         {
953 #ifndef BMCWEB_ENABLE_XTOKEN_AUTHENTICATION
954             messages::actionNotSupported(
955                 asyncResp->res, "Setting XToken when xtoken-auth feature "
956                                 "is disabled");
957             return;
958 #endif
959             authMethodsConfig.xtoken = *xToken;
960         }
961 
962         if (tls)
963         {
964 #ifndef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
965             messages::actionNotSupported(
966                 asyncResp->res, "Setting TLS when mutual-tls-auth feature "
967                                 "is disabled");
968             return;
969 #endif
970             authMethodsConfig.tls = *tls;
971         }
972 
973         if (!authMethodsConfig.basic && !authMethodsConfig.cookie &&
974             !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken &&
975             !authMethodsConfig.tls)
976         {
977             // Do not allow user to disable everything
978             messages::actionNotSupported(asyncResp->res,
979                                          "of disabling all available methods");
980             return;
981         }
982 
983         persistent_data::SessionStore::getInstance().updateAuthMethodsConfig(
984             authMethodsConfig);
985         // Save configuration immediately
986         persistent_data::getConfig().writeData();
987 
988         messages::success(asyncResp->res);
989     }
990 
991     /**
992      * @brief Get the required values from the given JSON, validates the
993      *        value and create the LDAP config object.
994      * @param input JSON data
995      * @param asyncResp pointer to the JSON response
996      * @param serverType Type of LDAP server(openLDAP/ActiveDirectory)
997      */
998 
999     void handleLDAPPatch(nlohmann::json& input,
1000                          const std::shared_ptr<AsyncResp>& asyncResp,
1001                          const std::string& serverType)
1002     {
1003         std::string dbusObjectPath;
1004         if (serverType == "ActiveDirectory")
1005         {
1006             dbusObjectPath = adConfigObject;
1007         }
1008         else if (serverType == "LDAP")
1009         {
1010             dbusObjectPath = ldapConfigObjectName;
1011         }
1012         else
1013         {
1014             return;
1015         }
1016 
1017         std::optional<nlohmann::json> authentication;
1018         std::optional<nlohmann::json> ldapService;
1019         std::optional<std::vector<std::string>> serviceAddressList;
1020         std::optional<bool> serviceEnabled;
1021         std::optional<std::vector<std::string>> baseDNList;
1022         std::optional<std::string> userNameAttribute;
1023         std::optional<std::string> groupsAttribute;
1024         std::optional<std::string> userName;
1025         std::optional<std::string> password;
1026         std::optional<std::vector<nlohmann::json>> remoteRoleMapData;
1027 
1028         if (!json_util::readJson(input, asyncResp->res, "Authentication",
1029                                  authentication, "LDAPService", ldapService,
1030                                  "ServiceAddresses", serviceAddressList,
1031                                  "ServiceEnabled", serviceEnabled,
1032                                  "RemoteRoleMapping", remoteRoleMapData))
1033         {
1034             return;
1035         }
1036 
1037         if (authentication)
1038         {
1039             parseLDAPAuthenticationJson(*authentication, asyncResp, userName,
1040                                         password);
1041         }
1042         if (ldapService)
1043         {
1044             parseLDAPServiceJson(*ldapService, asyncResp, baseDNList,
1045                                  userNameAttribute, groupsAttribute);
1046         }
1047         if (serviceAddressList)
1048         {
1049             if ((*serviceAddressList).size() == 0)
1050             {
1051                 messages::propertyValueNotInList(asyncResp->res, "[]",
1052                                                  "ServiceAddress");
1053                 return;
1054             }
1055         }
1056         if (baseDNList)
1057         {
1058             if ((*baseDNList).size() == 0)
1059             {
1060                 messages::propertyValueNotInList(asyncResp->res, "[]",
1061                                                  "BaseDistinguishedNames");
1062                 return;
1063             }
1064         }
1065 
1066         // nothing to update, then return
1067         if (!userName && !password && !serviceAddressList && !baseDNList &&
1068             !userNameAttribute && !groupsAttribute && !serviceEnabled &&
1069             !remoteRoleMapData)
1070         {
1071             return;
1072         }
1073 
1074         // Get the existing resource first then keep modifying
1075         // whenever any property gets updated.
1076         getLDAPConfigData(
1077             serverType, [this, asyncResp, userName, password, baseDNList,
1078                          userNameAttribute, groupsAttribute, serviceAddressList,
1079                          serviceEnabled, dbusObjectPath, remoteRoleMapData](
1080                             bool success, const LDAPConfigData& confData,
1081                             const std::string& serverT) {
1082                 if (!success)
1083                 {
1084                     messages::internalError(asyncResp->res);
1085                     return;
1086                 }
1087                 parseLDAPConfigData(asyncResp->res.jsonValue, confData,
1088                                     serverT);
1089                 if (confData.serviceEnabled)
1090                 {
1091                     // Disable the service first and update the rest of
1092                     // the properties.
1093                     handleServiceEnablePatch(false, asyncResp, serverT,
1094                                              dbusObjectPath);
1095                 }
1096 
1097                 if (serviceAddressList)
1098                 {
1099                     handleServiceAddressPatch(*serviceAddressList, asyncResp,
1100                                               serverT, dbusObjectPath);
1101                 }
1102                 if (userName)
1103                 {
1104                     handleUserNamePatch(*userName, asyncResp, serverT,
1105                                         dbusObjectPath);
1106                 }
1107                 if (password)
1108                 {
1109                     handlePasswordPatch(*password, asyncResp, serverT,
1110                                         dbusObjectPath);
1111                 }
1112 
1113                 if (baseDNList)
1114                 {
1115                     handleBaseDNPatch(*baseDNList, asyncResp, serverT,
1116                                       dbusObjectPath);
1117                 }
1118                 if (userNameAttribute)
1119                 {
1120                     handleUserNameAttrPatch(*userNameAttribute, asyncResp,
1121                                             serverT, dbusObjectPath);
1122                 }
1123                 if (groupsAttribute)
1124                 {
1125                     handleGroupNameAttrPatch(*groupsAttribute, asyncResp,
1126                                              serverT, dbusObjectPath);
1127                 }
1128                 if (serviceEnabled)
1129                 {
1130                     // if user has given the value as true then enable
1131                     // the service. if user has given false then no-op
1132                     // as service is already stopped.
1133                     if (*serviceEnabled)
1134                     {
1135                         handleServiceEnablePatch(*serviceEnabled, asyncResp,
1136                                                  serverT, dbusObjectPath);
1137                     }
1138                 }
1139                 else
1140                 {
1141                     // if user has not given the service enabled value
1142                     // then revert it to the same state as it was
1143                     // before.
1144                     handleServiceEnablePatch(confData.serviceEnabled, asyncResp,
1145                                              serverT, dbusObjectPath);
1146                 }
1147 
1148                 if (remoteRoleMapData)
1149                 {
1150                     handleRoleMapPatch(asyncResp, confData.groupRoleList,
1151                                        serverT, *remoteRoleMapData);
1152                 }
1153             });
1154     }
1155 
1156     void doGet(crow::Response& res, const crow::Request&,
1157                const std::vector<std::string>&) override
1158     {
1159         const persistent_data::AuthConfigMethods& authMethodsConfig =
1160             persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1161 
1162         auto asyncResp = std::make_shared<AsyncResp>(res);
1163         res.jsonValue = {
1164             {"@odata.id", "/redfish/v1/AccountService"},
1165             {"@odata.type", "#AccountService."
1166                             "v1_5_0.AccountService"},
1167             {"Id", "AccountService"},
1168             {"Name", "Account Service"},
1169             {"Description", "Account Service"},
1170             {"ServiceEnabled", true},
1171             {"MaxPasswordLength", 20},
1172             {"Accounts",
1173              {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}},
1174             {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}},
1175             {"Oem",
1176              {{"OpenBMC",
1177                {{"@odata.type", "#OemAccountService.v1_0_0.AccountService"},
1178                 {"AuthMethods",
1179                  {
1180                      {"BasicAuth", authMethodsConfig.basic},
1181                      {"SessionToken", authMethodsConfig.sessionToken},
1182                      {"XToken", authMethodsConfig.xtoken},
1183                      {"Cookie", authMethodsConfig.cookie},
1184                      {"TLS", authMethodsConfig.tls},
1185                  }}}}}},
1186             {"LDAP",
1187              {{"Certificates",
1188                {{"@odata.id",
1189                  "/redfish/v1/AccountService/LDAP/Certificates"}}}}}};
1190         crow::connections::systemBus->async_method_call(
1191             [asyncResp](
1192                 const boost::system::error_code ec,
1193                 const std::vector<std::pair<
1194                     std::string, std::variant<uint32_t, uint16_t, uint8_t>>>&
1195                     propertiesList) {
1196                 if (ec)
1197                 {
1198                     messages::internalError(asyncResp->res);
1199                     return;
1200                 }
1201                 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
1202                                  << "properties for AccountService";
1203                 for (const std::pair<std::string,
1204                                      std::variant<uint32_t, uint16_t, uint8_t>>&
1205                          property : propertiesList)
1206                 {
1207                     if (property.first == "MinPasswordLength")
1208                     {
1209                         const uint8_t* value =
1210                             std::get_if<uint8_t>(&property.second);
1211                         if (value != nullptr)
1212                         {
1213                             asyncResp->res.jsonValue["MinPasswordLength"] =
1214                                 *value;
1215                         }
1216                     }
1217                     if (property.first == "AccountUnlockTimeout")
1218                     {
1219                         const uint32_t* value =
1220                             std::get_if<uint32_t>(&property.second);
1221                         if (value != nullptr)
1222                         {
1223                             asyncResp->res.jsonValue["AccountLockoutDuration"] =
1224                                 *value;
1225                         }
1226                     }
1227                     if (property.first == "MaxLoginAttemptBeforeLockout")
1228                     {
1229                         const uint16_t* value =
1230                             std::get_if<uint16_t>(&property.second);
1231                         if (value != nullptr)
1232                         {
1233                             asyncResp->res
1234                                 .jsonValue["AccountLockoutThreshold"] = *value;
1235                         }
1236                     }
1237                 }
1238             },
1239             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1240             "org.freedesktop.DBus.Properties", "GetAll",
1241             "xyz.openbmc_project.User.AccountPolicy");
1242 
1243         auto callback = [asyncResp](bool success, LDAPConfigData& confData,
1244                                     const std::string& ldapType) {
1245             if (!success)
1246             {
1247                 return;
1248             }
1249             parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1250         };
1251 
1252         getLDAPConfigData("LDAP", callback);
1253         getLDAPConfigData("ActiveDirectory", callback);
1254     }
1255 
1256     void doPatch(crow::Response& res, const crow::Request& req,
1257                  const std::vector<std::string>&) override
1258     {
1259         auto asyncResp = std::make_shared<AsyncResp>(res);
1260 
1261         std::optional<uint32_t> unlockTimeout;
1262         std::optional<uint16_t> lockoutThreshold;
1263         std::optional<uint16_t> minPasswordLength;
1264         std::optional<uint16_t> maxPasswordLength;
1265         std::optional<nlohmann::json> ldapObject;
1266         std::optional<nlohmann::json> activeDirectoryObject;
1267         std::optional<nlohmann::json> oemObject;
1268 
1269         if (!json_util::readJson(
1270                 req, res, "AccountLockoutDuration", unlockTimeout,
1271                 "AccountLockoutThreshold", lockoutThreshold,
1272                 "MaxPasswordLength", maxPasswordLength, "MinPasswordLength",
1273                 minPasswordLength, "LDAP", ldapObject, "ActiveDirectory",
1274                 activeDirectoryObject, "Oem", oemObject))
1275         {
1276             return;
1277         }
1278 
1279         if (minPasswordLength)
1280         {
1281             messages::propertyNotWritable(asyncResp->res, "MinPasswordLength");
1282         }
1283 
1284         if (maxPasswordLength)
1285         {
1286             messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1287         }
1288 
1289         if (ldapObject)
1290         {
1291             handleLDAPPatch(*ldapObject, asyncResp, "LDAP");
1292         }
1293 
1294         if (std::optional<nlohmann::json> oemOpenBMCObject;
1295             oemObject &&
1296             json_util::readJson(*oemObject, res, "OpenBMC", oemOpenBMCObject))
1297         {
1298             if (std::optional<nlohmann::json> authMethodsObject;
1299                 oemOpenBMCObject &&
1300                 json_util::readJson(*oemOpenBMCObject, res, "AuthMethods",
1301                                     authMethodsObject))
1302             {
1303                 if (authMethodsObject)
1304                 {
1305                     handleAuthMethodsPatch(*authMethodsObject, asyncResp);
1306                 }
1307             }
1308         }
1309 
1310         if (activeDirectoryObject)
1311         {
1312             handleLDAPPatch(*activeDirectoryObject, asyncResp,
1313                             "ActiveDirectory");
1314         }
1315 
1316         if (unlockTimeout)
1317         {
1318             crow::connections::systemBus->async_method_call(
1319                 [asyncResp](const boost::system::error_code ec) {
1320                     if (ec)
1321                     {
1322                         messages::internalError(asyncResp->res);
1323                         return;
1324                     }
1325                     messages::success(asyncResp->res);
1326                 },
1327                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1328                 "org.freedesktop.DBus.Properties", "Set",
1329                 "xyz.openbmc_project.User.AccountPolicy",
1330                 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout));
1331         }
1332         if (lockoutThreshold)
1333         {
1334             crow::connections::systemBus->async_method_call(
1335                 [asyncResp](const boost::system::error_code ec) {
1336                     if (ec)
1337                     {
1338                         messages::internalError(asyncResp->res);
1339                         return;
1340                     }
1341                     messages::success(asyncResp->res);
1342                 },
1343                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1344                 "org.freedesktop.DBus.Properties", "Set",
1345                 "xyz.openbmc_project.User.AccountPolicy",
1346                 "MaxLoginAttemptBeforeLockout",
1347                 std::variant<uint16_t>(*lockoutThreshold));
1348         }
1349     }
1350 };
1351 
1352 class AccountsCollection : public Node
1353 {
1354   public:
1355     AccountsCollection(App& app) :
1356         Node(app, "/redfish/v1/AccountService/Accounts/")
1357     {
1358         entityPrivileges = {
1359             // According to the PrivilegeRegistry, GET should actually be
1360             // "Login". A "Login" only privilege would return an empty "Members"
1361             // list. Not going to worry about this since none of the defined
1362             // roles are just "Login". E.g. Readonly is {"Login",
1363             // "ConfigureSelf"}. In the rare event anyone defines a role that
1364             // has Login but not ConfigureSelf, implement this.
1365             {boost::beast::http::verb::get,
1366              {{"ConfigureUsers"}, {"ConfigureSelf"}}},
1367             {boost::beast::http::verb::head, {{"Login"}}},
1368             {boost::beast::http::verb::patch, {{"ConfigureUsers"}}},
1369             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1370             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1371             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1372     }
1373 
1374   private:
1375     void doGet(crow::Response& res, const crow::Request& req,
1376                const std::vector<std::string>&) override
1377     {
1378         auto asyncResp = std::make_shared<AsyncResp>(res);
1379         res.jsonValue = {{"@odata.id", "/redfish/v1/AccountService/Accounts"},
1380                          {"@odata.type", "#ManagerAccountCollection."
1381                                          "ManagerAccountCollection"},
1382                          {"Name", "Accounts Collection"},
1383                          {"Description", "BMC User Accounts"}};
1384 
1385         crow::connections::systemBus->async_method_call(
1386             [asyncResp, &req, this](const boost::system::error_code ec,
1387                                     const ManagedObjectType& users) {
1388                 if (ec)
1389                 {
1390                     messages::internalError(asyncResp->res);
1391                     return;
1392                 }
1393 
1394                 nlohmann::json& memberArray =
1395                     asyncResp->res.jsonValue["Members"];
1396                 memberArray = nlohmann::json::array();
1397 
1398                 for (auto& user : users)
1399                 {
1400                     const std::string& path =
1401                         static_cast<const std::string&>(user.first);
1402                     std::size_t lastIndex = path.rfind('/');
1403                     if (lastIndex == std::string::npos)
1404                     {
1405                         lastIndex = 0;
1406                     }
1407                     else
1408                     {
1409                         lastIndex += 1;
1410                     }
1411 
1412                     // As clarified by Redfish here:
1413                     // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1414                     // Users without ConfigureUsers, only see their own account.
1415                     // Users with ConfigureUsers, see all accounts.
1416                     if (req.session->username == path.substr(lastIndex) ||
1417                         isAllowedWithoutConfigureSelf(req))
1418                     {
1419                         memberArray.push_back(
1420                             {{"@odata.id",
1421                               "/redfish/v1/AccountService/Accounts/" +
1422                                   path.substr(lastIndex)}});
1423                     }
1424                 }
1425                 asyncResp->res.jsonValue["Members@odata.count"] =
1426                     memberArray.size();
1427             },
1428             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1429             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1430     }
1431     void doPost(crow::Response& res, const crow::Request& req,
1432                 const std::vector<std::string>&) override
1433     {
1434         auto asyncResp = std::make_shared<AsyncResp>(res);
1435 
1436         std::string username;
1437         std::string password;
1438         std::optional<std::string> roleId("User");
1439         std::optional<bool> enabled = true;
1440         if (!json_util::readJson(req, res, "UserName", username, "Password",
1441                                  password, "RoleId", roleId, "Enabled",
1442                                  enabled))
1443         {
1444             return;
1445         }
1446 
1447         std::string priv = getPrivilegeFromRoleId(*roleId);
1448         if (priv.empty())
1449         {
1450             messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId");
1451             return;
1452         }
1453         // TODO: Following override will be reverted once support in
1454         // phosphor-user-manager is added. In order to avoid dependency issues,
1455         // this is added in bmcweb, which will removed, once
1456         // phosphor-user-manager supports priv-noaccess.
1457         if (priv == "priv-noaccess")
1458         {
1459             roleId = "";
1460         }
1461         else
1462         {
1463             roleId = priv;
1464         }
1465 
1466         // Reading AllGroups property
1467         crow::connections::systemBus->async_method_call(
1468             [asyncResp, username, password{std::move(password)}, roleId,
1469              enabled](const boost::system::error_code ec,
1470                       const std::variant<std::vector<std::string>>& allGroups) {
1471                 if (ec)
1472                 {
1473                     BMCWEB_LOG_DEBUG << "ERROR with async_method_call";
1474                     messages::internalError(asyncResp->res);
1475                     return;
1476                 }
1477 
1478                 const std::vector<std::string>* allGroupsList =
1479                     std::get_if<std::vector<std::string>>(&allGroups);
1480 
1481                 if (allGroupsList == nullptr || allGroupsList->empty())
1482                 {
1483                     messages::internalError(asyncResp->res);
1484                     return;
1485                 }
1486 
1487                 crow::connections::systemBus->async_method_call(
1488                     [asyncResp, username,
1489                      password](const boost::system::error_code ec2,
1490                                sdbusplus::message::message& m) {
1491                         if (ec2)
1492                         {
1493                             userErrorMessageHandler(m.get_error(), asyncResp,
1494                                                     username, "");
1495                             return;
1496                         }
1497 
1498                         if (pamUpdatePassword(username, password) !=
1499                             PAM_SUCCESS)
1500                         {
1501                             // At this point we have a user that's been created,
1502                             // but the password set failed.Something is wrong,
1503                             // so delete the user that we've already created
1504                             crow::connections::systemBus->async_method_call(
1505                                 [asyncResp, password](
1506                                     const boost::system::error_code ec3) {
1507                                     if (ec3)
1508                                     {
1509                                         messages::internalError(asyncResp->res);
1510                                         return;
1511                                     }
1512 
1513                                     // If password is invalid
1514                                     messages::propertyValueFormatError(
1515                                         asyncResp->res, password, "Password");
1516                                 },
1517                                 "xyz.openbmc_project.User.Manager",
1518                                 "/xyz/openbmc_project/user/" + username,
1519                                 "xyz.openbmc_project.Object.Delete", "Delete");
1520 
1521                             BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1522                             return;
1523                         }
1524 
1525                         messages::created(asyncResp->res);
1526                         asyncResp->res.addHeader(
1527                             "Location",
1528                             "/redfish/v1/AccountService/Accounts/" + username);
1529                     },
1530                     "xyz.openbmc_project.User.Manager",
1531                     "/xyz/openbmc_project/user",
1532                     "xyz.openbmc_project.User.Manager", "CreateUser", username,
1533                     *allGroupsList, *roleId, *enabled);
1534             },
1535             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1536             "org.freedesktop.DBus.Properties", "Get",
1537             "xyz.openbmc_project.User.Manager", "AllGroups");
1538     }
1539 };
1540 
1541 class ManagerAccount : public Node
1542 {
1543   public:
1544     ManagerAccount(App& app) :
1545         Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string())
1546     {
1547         entityPrivileges = {
1548             {boost::beast::http::verb::get,
1549              {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}},
1550             {boost::beast::http::verb::head, {{"Login"}}},
1551             {boost::beast::http::verb::patch,
1552              {{"ConfigureUsers"}, {"ConfigureSelf"}}},
1553             {boost::beast::http::verb::put, {{"ConfigureUsers"}}},
1554             {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}},
1555             {boost::beast::http::verb::post, {{"ConfigureUsers"}}}};
1556     }
1557 
1558   private:
1559     void doGet(crow::Response& res, const crow::Request& req,
1560                const std::vector<std::string>& params) override
1561     {
1562         auto asyncResp = std::make_shared<AsyncResp>(res);
1563 
1564         if (params.size() != 1)
1565         {
1566             messages::internalError(asyncResp->res);
1567             return;
1568         }
1569 
1570         // Perform a proper ConfigureSelf authority check.  If the
1571         // user is operating on an account not their own, then their
1572         // ConfigureSelf privilege does not apply.  In this case,
1573         // perform the authority check again without the user's
1574         // ConfigureSelf privilege.
1575         if (req.session->username != params[0])
1576         {
1577             if (!isAllowedWithoutConfigureSelf(req))
1578             {
1579                 BMCWEB_LOG_DEBUG << "GET Account denied access";
1580                 messages::insufficientPrivilege(asyncResp->res);
1581                 return;
1582             }
1583         }
1584 
1585         crow::connections::systemBus->async_method_call(
1586             [asyncResp, accountName{std::string(params[0])}](
1587                 const boost::system::error_code ec,
1588                 const ManagedObjectType& users) {
1589                 if (ec)
1590                 {
1591                     messages::internalError(asyncResp->res);
1592                     return;
1593                 }
1594                 auto userIt = users.begin();
1595 
1596                 for (; userIt != users.end(); userIt++)
1597                 {
1598                     if (boost::ends_with(userIt->first.str, "/" + accountName))
1599                     {
1600                         break;
1601                     }
1602                 }
1603                 if (userIt == users.end())
1604                 {
1605                     messages::resourceNotFound(asyncResp->res, "ManagerAccount",
1606                                                accountName);
1607                     return;
1608                 }
1609 
1610                 asyncResp->res.jsonValue = {
1611                     {"@odata.type", "#ManagerAccount.v1_4_0.ManagerAccount"},
1612                     {"Name", "User Account"},
1613                     {"Description", "User Account"},
1614                     {"Password", nullptr},
1615                     {"AccountTypes", {"Redfish"}}};
1616 
1617                 for (const auto& interface : userIt->second)
1618                 {
1619                     if (interface.first ==
1620                         "xyz.openbmc_project.User.Attributes")
1621                     {
1622                         for (const auto& property : interface.second)
1623                         {
1624                             if (property.first == "UserEnabled")
1625                             {
1626                                 const bool* userEnabled =
1627                                     std::get_if<bool>(&property.second);
1628                                 if (userEnabled == nullptr)
1629                                 {
1630                                     BMCWEB_LOG_ERROR
1631                                         << "UserEnabled wasn't a bool";
1632                                     messages::internalError(asyncResp->res);
1633                                     return;
1634                                 }
1635                                 asyncResp->res.jsonValue["Enabled"] =
1636                                     *userEnabled;
1637                             }
1638                             else if (property.first ==
1639                                      "UserLockedForFailedAttempt")
1640                             {
1641                                 const bool* userLocked =
1642                                     std::get_if<bool>(&property.second);
1643                                 if (userLocked == nullptr)
1644                                 {
1645                                     BMCWEB_LOG_ERROR << "UserLockedForF"
1646                                                         "ailedAttempt "
1647                                                         "wasn't a bool";
1648                                     messages::internalError(asyncResp->res);
1649                                     return;
1650                                 }
1651                                 asyncResp->res.jsonValue["Locked"] =
1652                                     *userLocked;
1653                                 asyncResp->res.jsonValue
1654                                     ["Locked@Redfish.AllowableValues"] = {
1655                                     "false"}; // can only unlock accounts
1656                             }
1657                             else if (property.first == "UserPrivilege")
1658                             {
1659                                 const std::string* userPrivPtr =
1660                                     std::get_if<std::string>(&property.second);
1661                                 if (userPrivPtr == nullptr)
1662                                 {
1663                                     BMCWEB_LOG_ERROR
1664                                         << "UserPrivilege wasn't a "
1665                                            "string";
1666                                     messages::internalError(asyncResp->res);
1667                                     return;
1668                                 }
1669                                 std::string role =
1670                                     getRoleIdFromPrivilege(*userPrivPtr);
1671                                 if (role.empty())
1672                                 {
1673                                     BMCWEB_LOG_ERROR << "Invalid user role";
1674                                     messages::internalError(asyncResp->res);
1675                                     return;
1676                                 }
1677                                 asyncResp->res.jsonValue["RoleId"] = role;
1678 
1679                                 asyncResp->res.jsonValue["Links"]["Role"] = {
1680                                     {"@odata.id", "/redfish/v1/AccountService/"
1681                                                   "Roles/" +
1682                                                       role}};
1683                             }
1684                             else if (property.first == "UserPasswordExpired")
1685                             {
1686                                 const bool* userPasswordExpired =
1687                                     std::get_if<bool>(&property.second);
1688                                 if (userPasswordExpired == nullptr)
1689                                 {
1690                                     BMCWEB_LOG_ERROR << "UserPassword"
1691                                                         "Expired "
1692                                                         "wasn't a bool";
1693                                     messages::internalError(asyncResp->res);
1694                                     return;
1695                                 }
1696                                 asyncResp->res
1697                                     .jsonValue["PasswordChangeRequired"] =
1698                                     *userPasswordExpired;
1699                             }
1700                         }
1701                     }
1702                 }
1703 
1704                 asyncResp->res.jsonValue["@odata.id"] =
1705                     "/redfish/v1/AccountService/Accounts/" + accountName;
1706                 asyncResp->res.jsonValue["Id"] = accountName;
1707                 asyncResp->res.jsonValue["UserName"] = accountName;
1708             },
1709             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1710             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1711     }
1712 
1713     void doPatch(crow::Response& res, const crow::Request& req,
1714                  const std::vector<std::string>& params) override
1715     {
1716         auto asyncResp = std::make_shared<AsyncResp>(res);
1717         if (params.size() != 1)
1718         {
1719             messages::internalError(asyncResp->res);
1720             return;
1721         }
1722 
1723         std::optional<std::string> newUserName;
1724         std::optional<std::string> password;
1725         std::optional<bool> enabled;
1726         std::optional<std::string> roleId;
1727         std::optional<bool> locked;
1728         if (!json_util::readJson(req, res, "UserName", newUserName, "Password",
1729                                  password, "RoleId", roleId, "Enabled", enabled,
1730                                  "Locked", locked))
1731         {
1732             return;
1733         }
1734 
1735         const std::string& username = params[0];
1736 
1737         // Perform a proper ConfigureSelf authority check.  If the
1738         // session is being used to PATCH a property other than
1739         // Password, then the ConfigureSelf privilege does not apply.
1740         // If the user is operating on an account not their own, then
1741         // their ConfigureSelf privilege does not apply.  In either
1742         // case, perform the authority check again without the user's
1743         // ConfigureSelf privilege.
1744         if ((username != req.session->username) ||
1745             (newUserName || enabled || roleId || locked))
1746         {
1747             if (!isAllowedWithoutConfigureSelf(req))
1748             {
1749                 BMCWEB_LOG_WARNING << "PATCH Password denied access";
1750                 asyncResp->res.clear();
1751                 messages::insufficientPrivilege(asyncResp->res);
1752                 return;
1753             }
1754         }
1755 
1756         // if user name is not provided in the patch method or if it
1757         // matches the user name in the URI, then we are treating it as updating
1758         // user properties other then username. If username provided doesn't
1759         // match the URI, then we are treating this as user rename request.
1760         if (!newUserName || (newUserName.value() == username))
1761         {
1762             updateUserProperties(asyncResp, username, password, enabled, roleId,
1763                                  locked);
1764             return;
1765         }
1766         crow::connections::systemBus->async_method_call(
1767             [this, asyncResp, username, password(std::move(password)),
1768              roleId(std::move(roleId)), enabled,
1769              newUser{std::string(*newUserName)},
1770              locked](const boost::system::error_code ec,
1771                      sdbusplus::message::message& m) {
1772                 if (ec)
1773                 {
1774                     userErrorMessageHandler(m.get_error(), asyncResp, newUser,
1775                                             username);
1776                     return;
1777                 }
1778 
1779                 updateUserProperties(asyncResp, newUser, password, enabled,
1780                                      roleId, locked);
1781             },
1782             "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1783             "xyz.openbmc_project.User.Manager", "RenameUser", username,
1784             *newUserName);
1785     }
1786 
1787     void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp,
1788                               const std::string& username,
1789                               std::optional<std::string> password,
1790                               std::optional<bool> enabled,
1791                               std::optional<std::string> roleId,
1792                               std::optional<bool> locked)
1793     {
1794         std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username;
1795         dbus::utility::escapePathForDbus(dbusObjectPath);
1796 
1797         dbus::utility::checkDbusPathExists(
1798             dbusObjectPath,
1799             [dbusObjectPath(std::move(dbusObjectPath)), username,
1800              password(std::move(password)), roleId(std::move(roleId)), enabled,
1801              locked, asyncResp{std::move(asyncResp)}](int rc) {
1802                 if (!rc)
1803                 {
1804                     messages::resourceNotFound(
1805                         asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount",
1806                         username);
1807                     return;
1808                 }
1809 
1810                 if (password)
1811                 {
1812                     int retval = pamUpdatePassword(username, *password);
1813 
1814                     if (retval == PAM_USER_UNKNOWN)
1815                     {
1816                         messages::resourceNotFound(
1817                             asyncResp->res,
1818                             "#ManagerAccount.v1_4_0.ManagerAccount", username);
1819                     }
1820                     else if (retval == PAM_AUTHTOK_ERR)
1821                     {
1822                         // If password is invalid
1823                         messages::propertyValueFormatError(
1824                             asyncResp->res, *password, "Password");
1825                         BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1826                     }
1827                     else if (retval != PAM_SUCCESS)
1828                     {
1829                         messages::internalError(asyncResp->res);
1830                         return;
1831                     }
1832                 }
1833 
1834                 if (enabled)
1835                 {
1836                     crow::connections::systemBus->async_method_call(
1837                         [asyncResp](const boost::system::error_code ec) {
1838                             if (ec)
1839                             {
1840                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1841                                                  << ec;
1842                                 messages::internalError(asyncResp->res);
1843                                 return;
1844                             }
1845                             messages::success(asyncResp->res);
1846                             return;
1847                         },
1848                         "xyz.openbmc_project.User.Manager",
1849                         dbusObjectPath.c_str(),
1850                         "org.freedesktop.DBus.Properties", "Set",
1851                         "xyz.openbmc_project.User.Attributes", "UserEnabled",
1852                         std::variant<bool>{*enabled});
1853                 }
1854 
1855                 if (roleId)
1856                 {
1857                     std::string priv = getPrivilegeFromRoleId(*roleId);
1858                     if (priv.empty())
1859                     {
1860                         messages::propertyValueNotInList(asyncResp->res,
1861                                                          *roleId, "RoleId");
1862                         return;
1863                     }
1864                     if (priv == "priv-noaccess")
1865                     {
1866                         priv = "";
1867                     }
1868 
1869                     crow::connections::systemBus->async_method_call(
1870                         [asyncResp](const boost::system::error_code ec) {
1871                             if (ec)
1872                             {
1873                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1874                                                  << ec;
1875                                 messages::internalError(asyncResp->res);
1876                                 return;
1877                             }
1878                             messages::success(asyncResp->res);
1879                         },
1880                         "xyz.openbmc_project.User.Manager",
1881                         dbusObjectPath.c_str(),
1882                         "org.freedesktop.DBus.Properties", "Set",
1883                         "xyz.openbmc_project.User.Attributes", "UserPrivilege",
1884                         std::variant<std::string>{priv});
1885                 }
1886 
1887                 if (locked)
1888                 {
1889                     // admin can unlock the account which is locked by
1890                     // successive authentication failures but admin should
1891                     // not be allowed to lock an account.
1892                     if (*locked)
1893                     {
1894                         messages::propertyValueNotInList(asyncResp->res, "true",
1895                                                          "Locked");
1896                         return;
1897                     }
1898 
1899                     crow::connections::systemBus->async_method_call(
1900                         [asyncResp](const boost::system::error_code ec) {
1901                             if (ec)
1902                             {
1903                                 BMCWEB_LOG_ERROR << "D-Bus responses error: "
1904                                                  << ec;
1905                                 messages::internalError(asyncResp->res);
1906                                 return;
1907                             }
1908                             messages::success(asyncResp->res);
1909                             return;
1910                         },
1911                         "xyz.openbmc_project.User.Manager",
1912                         dbusObjectPath.c_str(),
1913                         "org.freedesktop.DBus.Properties", "Set",
1914                         "xyz.openbmc_project.User.Attributes",
1915                         "UserLockedForFailedAttempt",
1916                         std::variant<bool>{*locked});
1917                 }
1918             });
1919     }
1920 
1921     void doDelete(crow::Response& res, const crow::Request&,
1922                   const std::vector<std::string>& params) override
1923     {
1924         auto asyncResp = std::make_shared<AsyncResp>(res);
1925 
1926         if (params.size() != 1)
1927         {
1928             messages::internalError(asyncResp->res);
1929             return;
1930         }
1931 
1932         const std::string userPath = "/xyz/openbmc_project/user/" + params[0];
1933 
1934         crow::connections::systemBus->async_method_call(
1935             [asyncResp,
1936              username{params[0]}](const boost::system::error_code ec) {
1937                 if (ec)
1938                 {
1939                     messages::resourceNotFound(
1940                         asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount",
1941                         username);
1942                     return;
1943                 }
1944 
1945                 messages::accountRemoved(asyncResp->res);
1946             },
1947             "xyz.openbmc_project.User.Manager", userPath,
1948             "xyz.openbmc_project.Object.Delete", "Delete");
1949     }
1950 };
1951 
1952 } // namespace redfish
1953