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