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