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