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