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                     {"@odata.id", "/redfish/v1/AccountService#/Oem/OpenBMC"},
1291                     {"AuthMethods",
1292                      {
1293                          {"BasicAuth", authMethodsConfig.basic},
1294                          {"SessionToken", authMethodsConfig.sessionToken},
1295                          {"XToken", authMethodsConfig.xtoken},
1296                          {"Cookie", authMethodsConfig.cookie},
1297                          {"TLS", authMethodsConfig.tls},
1298                      }}}}}}};
1299             // /redfish/v1/AccountService/LDAP/Certificates is something only
1300             // ConfigureManager can access then only display when the user has
1301             // permissions ConfigureManager
1302             Privileges effectiveUserPrivileges =
1303                 redfish::getUserPrivileges(req.userRole);
1304 
1305             if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
1306                                                  effectiveUserPrivileges))
1307             {
1308                 asyncResp->res.jsonValue["LDAP"] = {
1309                     {"Certificates",
1310                      {{"@odata.id",
1311                        "/redfish/v1/AccountService/LDAP/Certificates"}}}};
1312             }
1313             crow::connections::systemBus->async_method_call(
1314                 [asyncResp](
1315                     const boost::system::error_code ec,
1316                     const std::vector<
1317                         std::pair<std::string,
1318                                   std::variant<uint32_t, uint16_t, uint8_t>>>&
1319                         propertiesList) {
1320                     if (ec)
1321                     {
1322                         messages::internalError(asyncResp->res);
1323                         return;
1324                     }
1325                     BMCWEB_LOG_DEBUG << "Got " << propertiesList.size()
1326                                      << "properties for AccountService";
1327                     for (const std::pair<std::string,
1328                                          std::variant<uint32_t, uint16_t,
1329                                                       uint8_t>>& property :
1330                          propertiesList)
1331                     {
1332                         if (property.first == "MinPasswordLength")
1333                         {
1334                             const uint8_t* value =
1335                                 std::get_if<uint8_t>(&property.second);
1336                             if (value != nullptr)
1337                             {
1338                                 asyncResp->res.jsonValue["MinPasswordLength"] =
1339                                     *value;
1340                             }
1341                         }
1342                         if (property.first == "AccountUnlockTimeout")
1343                         {
1344                             const uint32_t* value =
1345                                 std::get_if<uint32_t>(&property.second);
1346                             if (value != nullptr)
1347                             {
1348                                 asyncResp->res
1349                                     .jsonValue["AccountLockoutDuration"] =
1350                                     *value;
1351                             }
1352                         }
1353                         if (property.first == "MaxLoginAttemptBeforeLockout")
1354                         {
1355                             const uint16_t* value =
1356                                 std::get_if<uint16_t>(&property.second);
1357                             if (value != nullptr)
1358                             {
1359                                 asyncResp->res
1360                                     .jsonValue["AccountLockoutThreshold"] =
1361                                     *value;
1362                             }
1363                         }
1364                     }
1365                 },
1366                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1367                 "org.freedesktop.DBus.Properties", "GetAll",
1368                 "xyz.openbmc_project.User.AccountPolicy");
1369 
1370             auto callback = [asyncResp](bool success, LDAPConfigData& confData,
1371                                         const std::string& ldapType) {
1372                 if (!success)
1373                 {
1374                     return;
1375                 }
1376                 parseLDAPConfigData(asyncResp->res.jsonValue, confData,
1377                                     ldapType);
1378             };
1379 
1380             getLDAPConfigData("LDAP", callback);
1381             getLDAPConfigData("ActiveDirectory", callback);
1382         });
1383 
1384     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
1385         .privileges(redfish::privileges::getAccountService)
1386         .methods(boost::beast::http::verb::patch)(
1387             [](const crow::Request& req,
1388                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) -> void {
1389                 std::optional<uint32_t> unlockTimeout;
1390                 std::optional<uint16_t> lockoutThreshold;
1391                 std::optional<uint16_t> minPasswordLength;
1392                 std::optional<uint16_t> maxPasswordLength;
1393                 std::optional<nlohmann::json> ldapObject;
1394                 std::optional<nlohmann::json> activeDirectoryObject;
1395                 std::optional<nlohmann::json> oemObject;
1396 
1397                 if (!json_util::readJson(
1398                         req, asyncResp->res, "AccountLockoutDuration",
1399                         unlockTimeout, "AccountLockoutThreshold",
1400                         lockoutThreshold, "MaxPasswordLength",
1401                         maxPasswordLength, "MinPasswordLength",
1402                         minPasswordLength, "LDAP", ldapObject,
1403                         "ActiveDirectory", activeDirectoryObject, "Oem",
1404                         oemObject))
1405                 {
1406                     return;
1407                 }
1408 
1409                 if (minPasswordLength)
1410                 {
1411                     messages::propertyNotWritable(asyncResp->res,
1412                                                   "MinPasswordLength");
1413                 }
1414 
1415                 if (maxPasswordLength)
1416                 {
1417                     messages::propertyNotWritable(asyncResp->res,
1418                                                   "MaxPasswordLength");
1419                 }
1420 
1421                 if (ldapObject)
1422                 {
1423                     handleLDAPPatch(*ldapObject, asyncResp, "LDAP");
1424                 }
1425 
1426                 if (std::optional<nlohmann::json> oemOpenBMCObject;
1427                     oemObject &&
1428                     json_util::readJson(*oemObject, asyncResp->res, "OpenBMC",
1429                                         oemOpenBMCObject))
1430                 {
1431                     if (std::optional<nlohmann::json> authMethodsObject;
1432                         oemOpenBMCObject &&
1433                         json_util::readJson(*oemOpenBMCObject, asyncResp->res,
1434                                             "AuthMethods", authMethodsObject))
1435                     {
1436                         if (authMethodsObject)
1437                         {
1438                             handleAuthMethodsPatch(*authMethodsObject,
1439                                                    asyncResp);
1440                         }
1441                     }
1442                 }
1443 
1444                 if (activeDirectoryObject)
1445                 {
1446                     handleLDAPPatch(*activeDirectoryObject, asyncResp,
1447                                     "ActiveDirectory");
1448                 }
1449 
1450                 if (unlockTimeout)
1451                 {
1452                     crow::connections::systemBus->async_method_call(
1453                         [asyncResp](const boost::system::error_code ec) {
1454                             if (ec)
1455                             {
1456                                 messages::internalError(asyncResp->res);
1457                                 return;
1458                             }
1459                             messages::success(asyncResp->res);
1460                         },
1461                         "xyz.openbmc_project.User.Manager",
1462                         "/xyz/openbmc_project/user",
1463                         "org.freedesktop.DBus.Properties", "Set",
1464                         "xyz.openbmc_project.User.AccountPolicy",
1465                         "AccountUnlockTimeout",
1466                         std::variant<uint32_t>(*unlockTimeout));
1467                 }
1468                 if (lockoutThreshold)
1469                 {
1470                     crow::connections::systemBus->async_method_call(
1471                         [asyncResp](const boost::system::error_code ec) {
1472                             if (ec)
1473                             {
1474                                 messages::internalError(asyncResp->res);
1475                                 return;
1476                             }
1477                             messages::success(asyncResp->res);
1478                         },
1479                         "xyz.openbmc_project.User.Manager",
1480                         "/xyz/openbmc_project/user",
1481                         "org.freedesktop.DBus.Properties", "Set",
1482                         "xyz.openbmc_project.User.AccountPolicy",
1483                         "MaxLoginAttemptBeforeLockout",
1484                         std::variant<uint16_t>(*lockoutThreshold));
1485                 }
1486             });
1487 
1488     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
1489         .privileges(redfish::privileges::getManagerAccountCollection)
1490         .methods(boost::beast::http::verb::get)(
1491             [](const crow::Request& req,
1492                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) -> void {
1493                 asyncResp->res.jsonValue = {
1494                     {"@odata.id", "/redfish/v1/AccountService/Accounts"},
1495                     {"@odata.type", "#ManagerAccountCollection."
1496                                     "ManagerAccountCollection"},
1497                     {"Name", "Accounts Collection"},
1498                     {"Description", "BMC User Accounts"}};
1499 
1500                 Privileges effectiveUserPrivileges =
1501                     redfish::getUserPrivileges(req.userRole);
1502 
1503                 std::string thisUser = req.session->username;
1504 
1505                 crow::connections::systemBus->async_method_call(
1506                     [asyncResp, thisUser, effectiveUserPrivileges](
1507                         const boost::system::error_code ec,
1508                         const ManagedObjectType& users) {
1509                         if (ec)
1510                         {
1511                             messages::internalError(asyncResp->res);
1512                             return;
1513                         }
1514 
1515                         bool userCanSeeAllAccounts =
1516                             effectiveUserPrivileges.isSupersetOf(
1517                                 {"ConfigureUsers"});
1518 
1519                         bool userCanSeeSelf =
1520                             effectiveUserPrivileges.isSupersetOf(
1521                                 {"ConfigureSelf"});
1522 
1523                         nlohmann::json& memberArray =
1524                             asyncResp->res.jsonValue["Members"];
1525                         memberArray = nlohmann::json::array();
1526 
1527                         for (auto& userpath : users)
1528                         {
1529                             std::string user = userpath.first.filename();
1530                             if (user.empty())
1531                             {
1532                                 messages::internalError(asyncResp->res);
1533                                 BMCWEB_LOG_ERROR << "Invalid firmware ID";
1534 
1535                                 return;
1536                             }
1537 
1538                             // As clarified by Redfish here:
1539                             // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1540                             // Users without ConfigureUsers, only see their own
1541                             // account. Users with ConfigureUsers, see all
1542                             // accounts.
1543                             if (userCanSeeAllAccounts ||
1544                                 (thisUser == user && userCanSeeSelf))
1545                             {
1546                                 memberArray.push_back(
1547                                     {{"@odata.id",
1548                                       "/redfish/v1/AccountService/Accounts/" +
1549                                           user}});
1550                             }
1551                         }
1552                         asyncResp->res.jsonValue["Members@odata.count"] =
1553                             memberArray.size();
1554                     },
1555                     "xyz.openbmc_project.User.Manager",
1556                     "/xyz/openbmc_project/user",
1557                     "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1558             });
1559 
1560     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
1561         .privileges(redfish::privileges::postManagerAccountCollection)
1562         .methods(boost::beast::http::verb::post)([](const crow::Request& req,
1563                                                     const std::shared_ptr<
1564                                                         bmcweb::AsyncResp>&
1565                                                         asyncResp) -> void {
1566             std::string username;
1567             std::string password;
1568             std::optional<std::string> roleId("User");
1569             std::optional<bool> enabled = true;
1570             if (!json_util::readJson(req, asyncResp->res, "UserName", username,
1571                                      "Password", password, "RoleId", roleId,
1572                                      "Enabled", enabled))
1573             {
1574                 return;
1575             }
1576 
1577             std::string priv = getPrivilegeFromRoleId(*roleId);
1578             if (priv.empty())
1579             {
1580                 messages::propertyValueNotInList(asyncResp->res, *roleId,
1581                                                  "RoleId");
1582                 return;
1583             }
1584             // TODO: Following override will be reverted once support in
1585             // phosphor-user-manager is added. In order to avoid dependency
1586             // issues, this is added in bmcweb, which will removed, once
1587             // phosphor-user-manager supports priv-noaccess.
1588             if (priv == "priv-noaccess")
1589             {
1590                 roleId = "";
1591             }
1592             else
1593             {
1594                 roleId = priv;
1595             }
1596 
1597             // Reading AllGroups property
1598             crow::connections::systemBus->async_method_call(
1599                 [asyncResp, username, password{std::move(password)}, roleId,
1600                  enabled](
1601                     const boost::system::error_code ec,
1602                     const std::variant<std::vector<std::string>>& allGroups) {
1603                     if (ec)
1604                     {
1605                         BMCWEB_LOG_DEBUG << "ERROR with async_method_call";
1606                         messages::internalError(asyncResp->res);
1607                         return;
1608                     }
1609 
1610                     const std::vector<std::string>* allGroupsList =
1611                         std::get_if<std::vector<std::string>>(&allGroups);
1612 
1613                     if (allGroupsList == nullptr || allGroupsList->empty())
1614                     {
1615                         messages::internalError(asyncResp->res);
1616                         return;
1617                     }
1618 
1619                     crow::connections::systemBus->async_method_call(
1620                         [asyncResp, username,
1621                          password](const boost::system::error_code ec2,
1622                                    sdbusplus::message::message& m) {
1623                             if (ec2)
1624                             {
1625                                 userErrorMessageHandler(
1626                                     m.get_error(), asyncResp, username, "");
1627                                 return;
1628                             }
1629 
1630                             if (pamUpdatePassword(username, password) !=
1631                                 PAM_SUCCESS)
1632                             {
1633                                 // At this point we have a user that's been
1634                                 // created, but the password set
1635                                 // failed.Something is wrong, so delete the user
1636                                 // that we've already created
1637                                 crow::connections::systemBus->async_method_call(
1638                                     [asyncResp, password](
1639                                         const boost::system::error_code ec3) {
1640                                         if (ec3)
1641                                         {
1642                                             messages::internalError(
1643                                                 asyncResp->res);
1644                                             return;
1645                                         }
1646 
1647                                         // If password is invalid
1648                                         messages::propertyValueFormatError(
1649                                             asyncResp->res, password,
1650                                             "Password");
1651                                     },
1652                                     "xyz.openbmc_project.User.Manager",
1653                                     "/xyz/openbmc_project/user/" + username,
1654                                     "xyz.openbmc_project.Object.Delete",
1655                                     "Delete");
1656 
1657                                 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed";
1658                                 return;
1659                             }
1660 
1661                             messages::created(asyncResp->res);
1662                             asyncResp->res.addHeader(
1663                                 "Location",
1664                                 "/redfish/v1/AccountService/Accounts/" +
1665                                     username);
1666                         },
1667                         "xyz.openbmc_project.User.Manager",
1668                         "/xyz/openbmc_project/user",
1669                         "xyz.openbmc_project.User.Manager", "CreateUser",
1670                         username, *allGroupsList, *roleId, *enabled);
1671                 },
1672                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1673                 "org.freedesktop.DBus.Properties", "Get",
1674                 "xyz.openbmc_project.User.Manager", "AllGroups");
1675         });
1676 
1677     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
1678         .privileges(redfish::privileges::getManagerAccount)
1679         .methods(
1680             boost::beast::http::verb::
1681                 get)([](const crow::Request& req,
1682                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1683                         const std::string& accountName) -> void {
1684             if (req.session->username != accountName)
1685             {
1686                 // At this point we've determined that the user is trying to
1687                 // modify a user that isn't them.  We need to verify that they
1688                 // have permissions to modify other users, so re-run the auth
1689                 // check with the same permissions, minus ConfigureSelf.
1690                 Privileges effectiveUserPrivileges =
1691                     redfish::getUserPrivileges(req.userRole);
1692                 Privileges requiredPermissionsToChangeNonSelf = {
1693                     "ConfigureUsers", "ConfigureManager"};
1694                 if (!effectiveUserPrivileges.isSupersetOf(
1695                         requiredPermissionsToChangeNonSelf))
1696                 {
1697                     BMCWEB_LOG_DEBUG << "GET Account denied access";
1698                     messages::insufficientPrivilege(asyncResp->res);
1699                     return;
1700                 }
1701             }
1702 
1703             crow::connections::systemBus->async_method_call(
1704                 [asyncResp, accountName](const boost::system::error_code ec,
1705                                          const ManagedObjectType& users) {
1706                     if (ec)
1707                     {
1708                         messages::internalError(asyncResp->res);
1709                         return;
1710                     }
1711                     auto userIt = users.begin();
1712 
1713                     for (; userIt != users.end(); userIt++)
1714                     {
1715                         if (boost::ends_with(userIt->first.str,
1716                                              "/" + accountName))
1717                         {
1718                             break;
1719                         }
1720                     }
1721                     if (userIt == users.end())
1722                     {
1723                         messages::resourceNotFound(
1724                             asyncResp->res, "ManagerAccount", accountName);
1725                         return;
1726                     }
1727 
1728                     asyncResp->res.jsonValue = {
1729                         {"@odata.type",
1730                          "#ManagerAccount.v1_4_0.ManagerAccount"},
1731                         {"Name", "User Account"},
1732                         {"Description", "User Account"},
1733                         {"Password", nullptr},
1734                         {"AccountTypes", {"Redfish"}}};
1735 
1736                     for (const auto& interface : userIt->second)
1737                     {
1738                         if (interface.first ==
1739                             "xyz.openbmc_project.User.Attributes")
1740                         {
1741                             for (const auto& property : interface.second)
1742                             {
1743                                 if (property.first == "UserEnabled")
1744                                 {
1745                                     const bool* userEnabled =
1746                                         std::get_if<bool>(&property.second);
1747                                     if (userEnabled == nullptr)
1748                                     {
1749                                         BMCWEB_LOG_ERROR
1750                                             << "UserEnabled wasn't a bool";
1751                                         messages::internalError(asyncResp->res);
1752                                         return;
1753                                     }
1754                                     asyncResp->res.jsonValue["Enabled"] =
1755                                         *userEnabled;
1756                                 }
1757                                 else if (property.first ==
1758                                          "UserLockedForFailedAttempt")
1759                                 {
1760                                     const bool* userLocked =
1761                                         std::get_if<bool>(&property.second);
1762                                     if (userLocked == nullptr)
1763                                     {
1764                                         BMCWEB_LOG_ERROR << "UserLockedForF"
1765                                                             "ailedAttempt "
1766                                                             "wasn't a bool";
1767                                         messages::internalError(asyncResp->res);
1768                                         return;
1769                                     }
1770                                     asyncResp->res.jsonValue["Locked"] =
1771                                         *userLocked;
1772                                     asyncResp->res.jsonValue
1773                                         ["Locked@Redfish.AllowableValues"] = {
1774                                         "false"}; // can only unlock accounts
1775                                 }
1776                                 else if (property.first == "UserPrivilege")
1777                                 {
1778                                     const std::string* userPrivPtr =
1779                                         std::get_if<std::string>(
1780                                             &property.second);
1781                                     if (userPrivPtr == nullptr)
1782                                     {
1783                                         BMCWEB_LOG_ERROR
1784                                             << "UserPrivilege wasn't a "
1785                                                "string";
1786                                         messages::internalError(asyncResp->res);
1787                                         return;
1788                                     }
1789                                     std::string role =
1790                                         getRoleIdFromPrivilege(*userPrivPtr);
1791                                     if (role.empty())
1792                                     {
1793                                         BMCWEB_LOG_ERROR << "Invalid user role";
1794                                         messages::internalError(asyncResp->res);
1795                                         return;
1796                                     }
1797                                     asyncResp->res.jsonValue["RoleId"] = role;
1798 
1799                                     asyncResp->res.jsonValue["Links"]["Role"] =
1800                                         {{"@odata.id",
1801                                           "/redfish/v1/AccountService/"
1802                                           "Roles/" +
1803                                               role}};
1804                                 }
1805                                 else if (property.first ==
1806                                          "UserPasswordExpired")
1807                                 {
1808                                     const bool* userPasswordExpired =
1809                                         std::get_if<bool>(&property.second);
1810                                     if (userPasswordExpired == nullptr)
1811                                     {
1812                                         BMCWEB_LOG_ERROR << "UserPassword"
1813                                                             "Expired "
1814                                                             "wasn't a bool";
1815                                         messages::internalError(asyncResp->res);
1816                                         return;
1817                                     }
1818                                     asyncResp->res
1819                                         .jsonValue["PasswordChangeRequired"] =
1820                                         *userPasswordExpired;
1821                                 }
1822                             }
1823                         }
1824                     }
1825 
1826                     asyncResp->res.jsonValue["@odata.id"] =
1827                         "/redfish/v1/AccountService/Accounts/" + accountName;
1828                     asyncResp->res.jsonValue["Id"] = accountName;
1829                     asyncResp->res.jsonValue["UserName"] = accountName;
1830                 },
1831                 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1832                 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
1833         });
1834 
1835     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
1836         // TODO this privilege should be using the generated endpoints, but
1837         // because of the special handling of ConfigureSelf, it's not able to
1838         // yet
1839         .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}})
1840         .methods(boost::beast::http::verb::patch)(
1841             [](const crow::Request& req,
1842                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1843                const std::string& username) -> void {
1844                 std::optional<std::string> newUserName;
1845                 std::optional<std::string> password;
1846                 std::optional<bool> enabled;
1847                 std::optional<std::string> roleId;
1848                 std::optional<bool> locked;
1849                 if (!json_util::readJson(req, asyncResp->res, "UserName",
1850                                          newUserName, "Password", password,
1851                                          "RoleId", roleId, "Enabled", enabled,
1852                                          "Locked", locked))
1853                 {
1854                     return;
1855                 }
1856 
1857                 // Perform a proper ConfigureSelf authority check.  If the
1858                 // session is being used to PATCH a property other than
1859                 // Password, then the ConfigureSelf privilege does not apply.
1860                 // If the user is operating on an account not their own, then
1861                 // their ConfigureSelf privilege does not apply.  In either
1862                 // case, perform the authority check again without the user's
1863                 // ConfigureSelf privilege.
1864                 if ((username != req.session->username))
1865                 {
1866                     Privileges requiredPermissionsToChangeNonSelf = {
1867                         "ConfigureUsers"};
1868                     Privileges effectiveUserPrivileges =
1869                         redfish::getUserPrivileges(req.userRole);
1870 
1871                     if (!effectiveUserPrivileges.isSupersetOf(
1872                             requiredPermissionsToChangeNonSelf))
1873                     {
1874                         messages::insufficientPrivilege(asyncResp->res);
1875                         return;
1876                     }
1877                 }
1878 
1879                 // if user name is not provided in the patch method or if it
1880                 // matches the user name in the URI, then we are treating it as
1881                 // updating user properties other then username. If username
1882                 // provided doesn't match the URI, then we are treating this as
1883                 // user rename request.
1884                 if (!newUserName || (newUserName.value() == username))
1885                 {
1886                     updateUserProperties(asyncResp, username, password, enabled,
1887                                          roleId, locked);
1888                     return;
1889                 }
1890                 crow::connections::systemBus->async_method_call(
1891                     [asyncResp, username, password(std::move(password)),
1892                      roleId(std::move(roleId)), enabled,
1893                      newUser{std::string(*newUserName)},
1894                      locked](const boost::system::error_code ec,
1895                              sdbusplus::message::message& m) {
1896                         if (ec)
1897                         {
1898                             userErrorMessageHandler(m.get_error(), asyncResp,
1899                                                     newUser, username);
1900                             return;
1901                         }
1902 
1903                         updateUserProperties(asyncResp, newUser, password,
1904                                              enabled, roleId, locked);
1905                     },
1906                     "xyz.openbmc_project.User.Manager",
1907                     "/xyz/openbmc_project/user",
1908                     "xyz.openbmc_project.User.Manager", "RenameUser", username,
1909                     *newUserName);
1910             });
1911 
1912     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
1913         .privileges(redfish::privileges::deleteManagerAccount)
1914         .methods(boost::beast::http::verb::delete_)(
1915             [](const crow::Request& /*req*/,
1916                const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1917                const std::string& username) -> void {
1918                 const std::string userPath =
1919                     "/xyz/openbmc_project/user/" + username;
1920 
1921                 crow::connections::systemBus->async_method_call(
1922                     [asyncResp, username](const boost::system::error_code ec) {
1923                         if (ec)
1924                         {
1925                             messages::resourceNotFound(
1926                                 asyncResp->res,
1927                                 "#ManagerAccount.v1_4_0.ManagerAccount",
1928                                 username);
1929                             return;
1930                         }
1931 
1932                         messages::accountRemoved(asyncResp->res);
1933                     },
1934                     "xyz.openbmc_project.User.Manager", userPath,
1935                     "xyz.openbmc_project.Object.Delete", "Delete");
1936             });
1937 }
1938 
1939 } // namespace redfish
1940