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