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