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