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