xref: /openbmc/bmcweb/features/redfish/lib/account_service.hpp (revision 8b8444888a46062371689b10753e8641060fa8df)
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         case MTLSCommonNameParseMode::Whole:
1338         {
1339             return CertificateMappingAttribute::Whole;
1340         }
1341         case MTLSCommonNameParseMode::UserPrincipalName:
1342         {
1343             return CertificateMappingAttribute::UserPrincipalName;
1344         }
1345         default:
1346         {
1347             return CertificateMappingAttribute::Invalid;
1348         }
1349     }
1350 }
1351 
1352 inline void handleAccountServiceGet(
1353     App& app, const crow::Request& req,
1354     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1355 {
1356     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1357     {
1358         return;
1359     }
1360 
1361     if (req.session == nullptr)
1362     {
1363         messages::internalError(asyncResp->res);
1364         return;
1365     }
1366 
1367     const persistent_data::AuthConfigMethods& authMethodsConfig =
1368         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1369 
1370     asyncResp->res.addHeader(
1371         boost::beast::http::field::link,
1372         "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby");
1373 
1374     nlohmann::json& json = asyncResp->res.jsonValue;
1375     json["@odata.id"] = "/redfish/v1/AccountService";
1376     json["@odata.type"] = "#AccountService.v1_15_0.AccountService";
1377     json["Id"] = "AccountService";
1378     json["Name"] = "Account Service";
1379     json["Description"] = "Account Service";
1380     json["ServiceEnabled"] = true;
1381     json["MaxPasswordLength"] = 20;
1382     json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts";
1383     json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles";
1384     json["HTTPBasicAuth"] = authMethodsConfig.basic
1385                                 ? account_service::BasicAuthState::Enabled
1386                                 : account_service::BasicAuthState::Disabled;
1387 
1388     nlohmann::json::array_t allowed;
1389     allowed.emplace_back(account_service::BasicAuthState::Enabled);
1390     allowed.emplace_back(account_service::BasicAuthState::Disabled);
1391     json["HTTPBasicAuth@AllowableValues"] = std::move(allowed);
1392 
1393     nlohmann::json::object_t clientCertificate;
1394     clientCertificate["Enabled"] = authMethodsConfig.tls;
1395     clientCertificate["RespondToUnauthenticatedClients"] =
1396         !authMethodsConfig.tlsStrict;
1397 
1398     using account_service::CertificateMappingAttribute;
1399 
1400     CertificateMappingAttribute mapping =
1401         getCertificateMapping(authMethodsConfig.mTLSCommonNameParsingMode);
1402     if (mapping == CertificateMappingAttribute::Invalid)
1403     {
1404         messages::internalError(asyncResp->res);
1405     }
1406     else
1407     {
1408         clientCertificate["CertificateMappingAttribute"] = mapping;
1409     }
1410     nlohmann::json::object_t certificates;
1411     certificates["@odata.id"] =
1412         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates";
1413     certificates["@odata.type"] =
1414         "#CertificateCollection.CertificateCollection";
1415     clientCertificate["Certificates"] = std::move(certificates);
1416     json["MultiFactorAuth"]["ClientCertificate"] = std::move(clientCertificate);
1417 
1418     getClientCertificates(
1419         asyncResp,
1420         "/MultiFactorAuth/ClientCertificate/Certificates/Members"_json_pointer);
1421 
1422     json["Oem"]["OpenBMC"]["@odata.type"] =
1423         "#OpenBMCAccountService.v1_0_0.AccountService";
1424     json["Oem"]["OpenBMC"]["@odata.id"] =
1425         "/redfish/v1/AccountService#/Oem/OpenBMC";
1426     json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] =
1427         authMethodsConfig.basic;
1428     json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] =
1429         authMethodsConfig.sessionToken;
1430     json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken;
1431     json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie;
1432     json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls;
1433 
1434     // /redfish/v1/AccountService/LDAP/Certificates is something only
1435     // ConfigureManager can access then only display when the user has
1436     // permissions ConfigureManager
1437     Privileges effectiveUserPrivileges =
1438         redfish::getUserPrivileges(*req.session);
1439 
1440     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
1441                                          effectiveUserPrivileges))
1442     {
1443         asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] =
1444             "/redfish/v1/AccountService/LDAP/Certificates";
1445     }
1446     dbus::utility::getAllProperties(
1447         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1448         "xyz.openbmc_project.User.AccountPolicy",
1449         [asyncResp](const boost::system::error_code& ec,
1450                     const dbus::utility::DBusPropertiesMap& propertiesList) {
1451             if (ec)
1452             {
1453                 messages::internalError(asyncResp->res);
1454                 return;
1455             }
1456 
1457             BMCWEB_LOG_DEBUG("Got {} properties for AccountService",
1458                              propertiesList.size());
1459 
1460             const uint8_t* minPasswordLength = nullptr;
1461             const uint32_t* accountUnlockTimeout = nullptr;
1462             const uint16_t* maxLoginAttemptBeforeLockout = nullptr;
1463 
1464             const bool success = sdbusplus::unpackPropertiesNoThrow(
1465                 dbus_utils::UnpackErrorPrinter(), propertiesList,
1466                 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout",
1467                 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout",
1468                 maxLoginAttemptBeforeLockout);
1469 
1470             if (!success)
1471             {
1472                 messages::internalError(asyncResp->res);
1473                 return;
1474             }
1475 
1476             if (minPasswordLength != nullptr)
1477             {
1478                 asyncResp->res.jsonValue["MinPasswordLength"] =
1479                     *minPasswordLength;
1480             }
1481 
1482             if (accountUnlockTimeout != nullptr)
1483             {
1484                 asyncResp->res.jsonValue["AccountLockoutDuration"] =
1485                     *accountUnlockTimeout;
1486             }
1487 
1488             if (maxLoginAttemptBeforeLockout != nullptr)
1489             {
1490                 asyncResp->res.jsonValue["AccountLockoutThreshold"] =
1491                     *maxLoginAttemptBeforeLockout;
1492             }
1493         });
1494 
1495     auto callback = [asyncResp](bool success, const LDAPConfigData& confData,
1496                                 const std::string& ldapType) {
1497         if (!success)
1498         {
1499             return;
1500         }
1501         parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1502     };
1503 
1504     getLDAPConfigData("LDAP", callback);
1505     getLDAPConfigData("ActiveDirectory", callback);
1506 }
1507 
1508 inline void handleCertificateMappingAttributePatch(
1509     crow::Response& res, const std::string& certMapAttribute)
1510 {
1511     MTLSCommonNameParseMode parseMode =
1512         persistent_data::getMTLSCommonNameParseMode(certMapAttribute);
1513     if (parseMode == MTLSCommonNameParseMode::Invalid)
1514     {
1515         messages::propertyValueNotInList(res, "CertificateMappingAttribute",
1516                                          certMapAttribute);
1517         return;
1518     }
1519 
1520     persistent_data::AuthConfigMethods& authMethodsConfig =
1521         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1522     authMethodsConfig.mTLSCommonNameParsingMode = parseMode;
1523 }
1524 
1525 inline void handleRespondToUnauthenticatedClientsPatch(
1526     App& app, const crow::Request& req, crow::Response& res,
1527     bool respondToUnauthenticatedClients)
1528 {
1529     if (req.session != nullptr)
1530     {
1531         // Sanity check.  If the user isn't currently authenticated with mutual
1532         // TLS, they very likely are about to permanently lock themselves out.
1533         // Make sure they're using mutual TLS before allowing locking.
1534         if (req.session->sessionType != persistent_data::SessionType::MutualTLS)
1535         {
1536             messages::propertyValueExternalConflict(
1537                 res,
1538                 "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients",
1539                 respondToUnauthenticatedClients);
1540             return;
1541         }
1542     }
1543 
1544     persistent_data::AuthConfigMethods& authMethodsConfig =
1545         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1546 
1547     // Change the settings
1548     authMethodsConfig.tlsStrict = !respondToUnauthenticatedClients;
1549 
1550     // Write settings to disk
1551     persistent_data::getConfig().writeData();
1552 
1553     // Trigger a reload, to apply the new settings to new connections
1554     app.loadCertificate();
1555 }
1556 
1557 inline void handleAccountServicePatch(
1558     App& app, const crow::Request& req,
1559     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1560 {
1561     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1562     {
1563         return;
1564     }
1565     std::optional<uint32_t> unlockTimeout;
1566     std::optional<uint16_t> lockoutThreshold;
1567     std::optional<uint8_t> minPasswordLength;
1568     std::optional<uint16_t> maxPasswordLength;
1569     LdapPatchParams ldapObject;
1570     std::optional<std::string> certificateMappingAttribute;
1571     std::optional<bool> respondToUnauthenticatedClients;
1572     LdapPatchParams activeDirectoryObject;
1573     AuthMethods auth;
1574     std::optional<std::string> httpBasicAuth;
1575 
1576     if (!json_util::readJsonPatch(                                         //
1577             req, asyncResp->res,                                           //
1578             "AccountLockoutDuration", unlockTimeout,                       //
1579             "AccountLockoutThreshold", lockoutThreshold,                   //
1580             "ActiveDirectory/Authentication/AuthenticationType",
1581             activeDirectoryObject.authType,                                //
1582             "ActiveDirectory/Authentication/Password",
1583             activeDirectoryObject.password,                                //
1584             "ActiveDirectory/Authentication/Username",
1585             activeDirectoryObject.userName,                                //
1586             "ActiveDirectory/LDAPService/SearchSettings/BaseDistinguishedNames",
1587             activeDirectoryObject.baseDNList,                              //
1588             "ActiveDirectory/LDAPService/SearchSettings/GroupsAttribute",
1589             activeDirectoryObject.groupsAttribute,                         //
1590             "ActiveDirectory/LDAPService/SearchSettings/UsernameAttribute",
1591             activeDirectoryObject.userNameAttribute,                       //
1592             "ActiveDirectory/RemoteRoleMapping",
1593             activeDirectoryObject.remoteRoleMapData,                       //
1594             "ActiveDirectory/ServiceAddresses",
1595             activeDirectoryObject.serviceAddressList,                      //
1596             "ActiveDirectory/ServiceEnabled",
1597             activeDirectoryObject.serviceEnabled,                          //
1598             "HTTPBasicAuth", httpBasicAuth,                                //
1599             "LDAP/Authentication/AuthenticationType", ldapObject.authType, //
1600             "LDAP/Authentication/Password", ldapObject.password,           //
1601             "LDAP/Authentication/Username", ldapObject.userName,           //
1602             "LDAP/LDAPService/SearchSettings/BaseDistinguishedNames",
1603             ldapObject.baseDNList,                                         //
1604             "LDAP/LDAPService/SearchSettings/GroupsAttribute",
1605             ldapObject.groupsAttribute,                                    //
1606             "LDAP/LDAPService/SearchSettings/UsernameAttribute",
1607             ldapObject.userNameAttribute,                                  //
1608             "LDAP/RemoteRoleMapping", ldapObject.remoteRoleMapData,        //
1609             "LDAP/ServiceAddresses", ldapObject.serviceAddressList,        //
1610             "LDAP/ServiceEnabled", ldapObject.serviceEnabled,              //
1611             "MaxPasswordLength", maxPasswordLength,                        //
1612             "MinPasswordLength", minPasswordLength,                        //
1613             "MultiFactorAuth/ClientCertificate/CertificateMappingAttribute",
1614             certificateMappingAttribute,                                   //
1615             "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients",
1616             respondToUnauthenticatedClients,                               //
1617             "Oem/OpenBMC/AuthMethods/BasicAuth", auth.basicAuth,           //
1618             "Oem/OpenBMC/AuthMethods/Cookie", auth.cookie,                 //
1619             "Oem/OpenBMC/AuthMethods/SessionToken", auth.sessionToken,     //
1620             "Oem/OpenBMC/AuthMethods/TLS", auth.tls,                       //
1621             "Oem/OpenBMC/AuthMethods/XToken", auth.xToken                  //
1622             ))
1623     {
1624         return;
1625     }
1626 
1627     if (httpBasicAuth)
1628     {
1629         if (*httpBasicAuth == "Enabled")
1630         {
1631             auth.basicAuth = true;
1632         }
1633         else if (*httpBasicAuth == "Disabled")
1634         {
1635             auth.basicAuth = false;
1636         }
1637         else
1638         {
1639             messages::propertyValueNotInList(asyncResp->res, "HttpBasicAuth",
1640                                              *httpBasicAuth);
1641         }
1642     }
1643 
1644     if (respondToUnauthenticatedClients)
1645     {
1646         handleRespondToUnauthenticatedClientsPatch(
1647             app, req, asyncResp->res, *respondToUnauthenticatedClients);
1648     }
1649 
1650     if (certificateMappingAttribute)
1651     {
1652         handleCertificateMappingAttributePatch(asyncResp->res,
1653                                                *certificateMappingAttribute);
1654     }
1655 
1656     if (minPasswordLength)
1657     {
1658         setDbusProperty(
1659             asyncResp, "MinPasswordLength", "xyz.openbmc_project.User.Manager",
1660             sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1661             "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength",
1662             *minPasswordLength);
1663     }
1664 
1665     if (maxPasswordLength)
1666     {
1667         messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1668     }
1669 
1670     handleLDAPPatch(std::move(activeDirectoryObject), asyncResp,
1671                     "ActiveDirectory");
1672     handleLDAPPatch(std::move(ldapObject), asyncResp, "LDAP");
1673 
1674     handleAuthMethodsPatch(asyncResp, auth);
1675 
1676     if (unlockTimeout)
1677     {
1678         setDbusProperty(
1679             asyncResp, "AccountLockoutDuration",
1680             "xyz.openbmc_project.User.Manager",
1681             sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1682             "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout",
1683             *unlockTimeout);
1684     }
1685     if (lockoutThreshold)
1686     {
1687         setDbusProperty(
1688             asyncResp, "AccountLockoutThreshold",
1689             "xyz.openbmc_project.User.Manager",
1690             sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1691             "xyz.openbmc_project.User.AccountPolicy",
1692             "MaxLoginAttemptBeforeLockout", *lockoutThreshold);
1693     }
1694 }
1695 
1696 inline void handleAccountCollectionHead(
1697     App& app, const crow::Request& req,
1698     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1699 {
1700     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1701     {
1702         return;
1703     }
1704     asyncResp->res.addHeader(
1705         boost::beast::http::field::link,
1706         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1707 }
1708 
1709 inline void handleAccountCollectionGet(
1710     App& app, const crow::Request& req,
1711     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1712 {
1713     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1714     {
1715         return;
1716     }
1717 
1718     if (req.session == nullptr)
1719     {
1720         messages::internalError(asyncResp->res);
1721         return;
1722     }
1723 
1724     asyncResp->res.addHeader(
1725         boost::beast::http::field::link,
1726         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1727 
1728     asyncResp->res.jsonValue["@odata.id"] =
1729         "/redfish/v1/AccountService/Accounts";
1730     asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection."
1731                                               "ManagerAccountCollection";
1732     asyncResp->res.jsonValue["Name"] = "Accounts Collection";
1733     asyncResp->res.jsonValue["Description"] = "BMC User Accounts";
1734 
1735     Privileges effectiveUserPrivileges =
1736         redfish::getUserPrivileges(*req.session);
1737 
1738     std::string thisUser;
1739     if (req.session)
1740     {
1741         thisUser = req.session->username;
1742     }
1743     sdbusplus::message::object_path path("/xyz/openbmc_project/user");
1744     dbus::utility::getManagedObjects(
1745         "xyz.openbmc_project.User.Manager", path,
1746         [asyncResp, thisUser, effectiveUserPrivileges](
1747             const boost::system::error_code& ec,
1748             const dbus::utility::ManagedObjectType& users) {
1749             if (ec)
1750             {
1751                 messages::internalError(asyncResp->res);
1752                 return;
1753             }
1754 
1755             bool userCanSeeAllAccounts =
1756                 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"});
1757 
1758             bool userCanSeeSelf =
1759                 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"});
1760 
1761             nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
1762             memberArray = nlohmann::json::array();
1763 
1764             for (const auto& userpath : users)
1765             {
1766                 std::string user = userpath.first.filename();
1767                 if (user.empty())
1768                 {
1769                     messages::internalError(asyncResp->res);
1770                     BMCWEB_LOG_ERROR("Invalid firmware ID");
1771 
1772                     return;
1773                 }
1774 
1775                 // As clarified by Redfish here:
1776                 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1777                 // Users without ConfigureUsers, only see their own
1778                 // account. Users with ConfigureUsers, see all
1779                 // accounts.
1780                 if (userCanSeeAllAccounts ||
1781                     (thisUser == user && userCanSeeSelf))
1782                 {
1783                     nlohmann::json::object_t member;
1784                     member["@odata.id"] = boost::urls::format(
1785                         "/redfish/v1/AccountService/Accounts/{}", user);
1786                     memberArray.emplace_back(std::move(member));
1787                 }
1788             }
1789             asyncResp->res.jsonValue["Members@odata.count"] =
1790                 memberArray.size();
1791         });
1792 }
1793 
1794 inline void processAfterCreateUser(
1795     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1796     const std::string& username, const std::string& password,
1797     const boost::system::error_code& ec, sdbusplus::message_t& m)
1798 {
1799     if (ec)
1800     {
1801         userErrorMessageHandler(m.get_error(), asyncResp, username, "");
1802         return;
1803     }
1804 
1805     if (pamUpdatePassword(username, password) != PAM_SUCCESS)
1806     {
1807         // At this point we have a user that's been
1808         // created, but the password set
1809         // failed.Something is wrong, so delete the user
1810         // that we've already created
1811         sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1812         tempObjPath /= username;
1813         const std::string userPath(tempObjPath);
1814 
1815         dbus::utility::async_method_call(
1816             asyncResp,
1817             [asyncResp, password](const boost::system::error_code& ec3) {
1818                 if (ec3)
1819                 {
1820                     messages::internalError(asyncResp->res);
1821                     return;
1822                 }
1823 
1824                 // If password is invalid
1825                 messages::propertyValueFormatError(asyncResp->res, nullptr,
1826                                                    "Password");
1827             },
1828             "xyz.openbmc_project.User.Manager", userPath,
1829             "xyz.openbmc_project.Object.Delete", "Delete");
1830 
1831         BMCWEB_LOG_ERROR("pamUpdatePassword Failed");
1832         return;
1833     }
1834 
1835     messages::created(asyncResp->res);
1836     asyncResp->res.addHeader("Location",
1837                              "/redfish/v1/AccountService/Accounts/" + username);
1838 }
1839 
1840 inline void processAfterGetAllGroups(
1841     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1842     const std::string& username, const std::string& password,
1843     const std::string& roleId, bool enabled,
1844     std::optional<std::vector<std::string>> accountTypes,
1845     const std::vector<std::string>& allGroupsList)
1846 {
1847     std::vector<std::string> userGroups;
1848     std::vector<std::string> accountTypeUserGroups;
1849 
1850     // If user specified account types then convert them to unix user groups
1851     if (accountTypes)
1852     {
1853         if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes,
1854                                          accountTypeUserGroups))
1855         {
1856             // Problem in mapping Account Types to User Groups, Error already
1857             // logged.
1858             return;
1859         }
1860     }
1861 
1862     for (const auto& grp : allGroupsList)
1863     {
1864         // If user specified the account type then only accept groups which are
1865         // in the account types group list.
1866         if (!accountTypeUserGroups.empty())
1867         {
1868             bool found = false;
1869             for (const auto& grp1 : accountTypeUserGroups)
1870             {
1871                 if (grp == grp1)
1872                 {
1873                     found = true;
1874                     break;
1875                 }
1876             }
1877             if (!found)
1878             {
1879                 continue;
1880             }
1881         }
1882 
1883         // Console access is provided to the user who is a member of
1884         // hostconsole group and has a administrator role. So, set
1885         // hostconsole group only for the administrator.
1886         if ((grp == "hostconsole") && (roleId != "priv-admin"))
1887         {
1888             if (!accountTypeUserGroups.empty())
1889             {
1890                 BMCWEB_LOG_ERROR(
1891                     "Only administrator can get HostConsole access");
1892                 asyncResp->res.result(boost::beast::http::status::bad_request);
1893                 return;
1894             }
1895             continue;
1896         }
1897         userGroups.emplace_back(grp);
1898     }
1899 
1900     // Make sure user specified groups are valid. This is internal error because
1901     // it some inconsistencies between user manager and bmcweb.
1902     if (!accountTypeUserGroups.empty() &&
1903         accountTypeUserGroups.size() != userGroups.size())
1904     {
1905         messages::internalError(asyncResp->res);
1906         return;
1907     }
1908     dbus::utility::async_method_call(
1909         asyncResp,
1910         [asyncResp, username, password](const boost::system::error_code& ec2,
1911                                         sdbusplus::message_t& m) {
1912             processAfterCreateUser(asyncResp, username, password, ec2, m);
1913         },
1914         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1915         "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups,
1916         roleId, enabled);
1917 }
1918 
1919 inline void handleAccountCollectionPost(
1920     App& app, const crow::Request& req,
1921     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1922 {
1923     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1924     {
1925         return;
1926     }
1927     std::string username;
1928     std::string password;
1929     std::optional<std::string> roleIdJson;
1930     std::optional<bool> enabledJson;
1931     std::optional<std::vector<std::string>> accountTypes;
1932     if (!json_util::readJsonPatch(        //
1933             req, asyncResp->res,          //
1934             "AccountTypes", accountTypes, //
1935             "Enabled", enabledJson,       //
1936             "Password", password,         //
1937             "RoleId", roleIdJson,         //
1938             "UserName", username          //
1939             ))
1940     {
1941         return;
1942     }
1943 
1944     std::string roleId = roleIdJson.value_or("User");
1945     std::string priv = getPrivilegeFromRoleId(roleId);
1946     if (priv.empty())
1947     {
1948         messages::propertyValueNotInList(asyncResp->res, roleId, "RoleId");
1949         return;
1950     }
1951     roleId = priv;
1952 
1953     bool enabled = enabledJson.value_or(true);
1954 
1955     // Reading AllGroups property
1956     dbus::utility::getProperty<std::vector<std::string>>(
1957         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1958         "xyz.openbmc_project.User.Manager", "AllGroups",
1959         [asyncResp, username, password{std::move(password)}, roleId, enabled,
1960          accountTypes](const boost::system::error_code& ec,
1961                        const std::vector<std::string>& allGroupsList) {
1962             if (ec)
1963             {
1964                 BMCWEB_LOG_ERROR("D-Bus response error {}", ec);
1965                 messages::internalError(asyncResp->res);
1966                 return;
1967             }
1968 
1969             if (allGroupsList.empty())
1970             {
1971                 messages::internalError(asyncResp->res);
1972                 return;
1973             }
1974 
1975             processAfterGetAllGroups(asyncResp, username, password, roleId,
1976                                      enabled, accountTypes, allGroupsList);
1977         });
1978 }
1979 
1980 inline void handleAccountHead(
1981     App& app, const crow::Request& req,
1982     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1983     const std::string& /*accountName*/)
1984 {
1985     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1986     {
1987         return;
1988     }
1989     asyncResp->res.addHeader(
1990         boost::beast::http::field::link,
1991         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1992 }
1993 
1994 inline void handleAccountGet(
1995     App& app, const crow::Request& req,
1996     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1997     const std::string& accountName)
1998 {
1999     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2000     {
2001         return;
2002     }
2003     asyncResp->res.addHeader(
2004         boost::beast::http::field::link,
2005         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
2006 
2007     if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2008     {
2009         // If authentication is disabled, there are no user accounts
2010         messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2011                                    accountName);
2012         return;
2013     }
2014 
2015     if (req.session == nullptr)
2016     {
2017         messages::internalError(asyncResp->res);
2018         return;
2019     }
2020     if (req.session->username != accountName)
2021     {
2022         // At this point we've determined that the user is trying to
2023         // modify a user that isn't them.  We need to verify that they
2024         // have permissions to modify other users, so re-run the auth
2025         // check with the same permissions, minus ConfigureSelf.
2026         Privileges effectiveUserPrivileges =
2027             redfish::getUserPrivileges(*req.session);
2028         Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers",
2029                                                          "ConfigureManager"};
2030         if (!effectiveUserPrivileges.isSupersetOf(
2031                 requiredPermissionsToChangeNonSelf))
2032         {
2033             BMCWEB_LOG_DEBUG("GET Account denied access");
2034             messages::insufficientPrivilege(asyncResp->res);
2035             return;
2036         }
2037     }
2038 
2039     sdbusplus::message::object_path path("/xyz/openbmc_project/user");
2040     dbus::utility::getManagedObjects(
2041         "xyz.openbmc_project.User.Manager", path,
2042         [asyncResp,
2043          accountName](const boost::system::error_code& ec,
2044                       const dbus::utility::ManagedObjectType& users) {
2045             if (ec)
2046             {
2047                 messages::internalError(asyncResp->res);
2048                 return;
2049             }
2050             const auto userIt = std::ranges::find_if(
2051                 users,
2052                 [accountName](
2053                     const std::pair<sdbusplus::message::object_path,
2054                                     dbus::utility::DBusInterfacesMap>& user) {
2055                     return accountName == user.first.filename();
2056                 });
2057 
2058             if (userIt == users.end())
2059             {
2060                 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2061                                            accountName);
2062                 return;
2063             }
2064 
2065             asyncResp->res.jsonValue["@odata.type"] =
2066                 "#ManagerAccount.v1_7_0.ManagerAccount";
2067             asyncResp->res.jsonValue["Name"] = "User Account";
2068             asyncResp->res.jsonValue["Description"] = "User Account";
2069             asyncResp->res.jsonValue["Password"] = nullptr;
2070             asyncResp->res.jsonValue["StrictAccountTypes"] = true;
2071 
2072             for (const auto& interface : userIt->second)
2073             {
2074                 if (interface.first == "xyz.openbmc_project.User.Attributes")
2075                 {
2076                     const bool* userEnabled = nullptr;
2077                     const bool* userLocked = nullptr;
2078                     const std::string* userPrivPtr = nullptr;
2079                     const bool* userPasswordExpired = nullptr;
2080                     const std::vector<std::string>* userGroups = nullptr;
2081 
2082                     const bool success = sdbusplus::unpackPropertiesNoThrow(
2083                         dbus_utils::UnpackErrorPrinter(), interface.second,
2084                         "UserEnabled", userEnabled,
2085                         "UserLockedForFailedAttempt", userLocked,
2086                         "UserPrivilege", userPrivPtr, "UserPasswordExpired",
2087                         userPasswordExpired, "UserGroups", userGroups);
2088                     if (!success)
2089                     {
2090                         messages::internalError(asyncResp->res);
2091                         return;
2092                     }
2093                     if (userEnabled == nullptr)
2094                     {
2095                         BMCWEB_LOG_ERROR("UserEnabled wasn't a bool");
2096                         messages::internalError(asyncResp->res);
2097                         return;
2098                     }
2099                     asyncResp->res.jsonValue["Enabled"] = *userEnabled;
2100 
2101                     if (userLocked == nullptr)
2102                     {
2103                         BMCWEB_LOG_ERROR("UserLockedForF"
2104                                          "ailedAttempt "
2105                                          "wasn't a bool");
2106                         messages::internalError(asyncResp->res);
2107                         return;
2108                     }
2109                     asyncResp->res.jsonValue["Locked"] = *userLocked;
2110                     nlohmann::json::array_t allowed;
2111                     // can only unlock accounts
2112                     allowed.emplace_back("false");
2113                     asyncResp->res.jsonValue["Locked@Redfish.AllowableValues"] =
2114                         std::move(allowed);
2115 
2116                     if (userPrivPtr == nullptr)
2117                     {
2118                         BMCWEB_LOG_ERROR("UserPrivilege wasn't a "
2119                                          "string");
2120                         messages::internalError(asyncResp->res);
2121                         return;
2122                     }
2123                     std::string role = getRoleIdFromPrivilege(*userPrivPtr);
2124                     if (role.empty())
2125                     {
2126                         BMCWEB_LOG_ERROR("Invalid user role");
2127                         messages::internalError(asyncResp->res);
2128                         return;
2129                     }
2130                     asyncResp->res.jsonValue["RoleId"] = role;
2131 
2132                     nlohmann::json& roleEntry =
2133                         asyncResp->res.jsonValue["Links"]["Role"];
2134                     roleEntry["@odata.id"] = boost::urls::format(
2135                         "/redfish/v1/AccountService/Roles/{}", role);
2136 
2137                     if (userPasswordExpired == nullptr)
2138                     {
2139                         BMCWEB_LOG_ERROR("UserPasswordExpired wasn't a bool");
2140                         messages::internalError(asyncResp->res);
2141                         return;
2142                     }
2143                     asyncResp->res.jsonValue["PasswordChangeRequired"] =
2144                         *userPasswordExpired;
2145 
2146                     if (userGroups == nullptr)
2147                     {
2148                         BMCWEB_LOG_ERROR("userGroups wasn't a string vector");
2149                         messages::internalError(asyncResp->res);
2150                         return;
2151                     }
2152                     if (!translateUserGroup(*userGroups, asyncResp->res))
2153                     {
2154                         BMCWEB_LOG_ERROR("userGroups mapping failed");
2155                         messages::internalError(asyncResp->res);
2156                         return;
2157                     }
2158                 }
2159             }
2160 
2161             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2162                 "/redfish/v1/AccountService/Accounts/{}", accountName);
2163             asyncResp->res.jsonValue["Id"] = accountName;
2164             asyncResp->res.jsonValue["UserName"] = accountName;
2165         });
2166 }
2167 
2168 inline void handleAccountDelete(
2169     App& app, const crow::Request& req,
2170     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2171     const std::string& username)
2172 {
2173     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2174     {
2175         return;
2176     }
2177 
2178     if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2179     {
2180         // If authentication is disabled, there are no user accounts
2181         messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2182         return;
2183     }
2184     sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
2185     tempObjPath /= username;
2186     const std::string userPath(tempObjPath);
2187 
2188     dbus::utility::async_method_call(
2189         asyncResp,
2190         [asyncResp, username](const boost::system::error_code& ec) {
2191             if (ec)
2192             {
2193                 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2194                                            username);
2195                 return;
2196             }
2197 
2198             messages::accountRemoved(asyncResp->res);
2199         },
2200         "xyz.openbmc_project.User.Manager", userPath,
2201         "xyz.openbmc_project.Object.Delete", "Delete");
2202 }
2203 
2204 inline void handleAccountPatch(
2205     App& app, const crow::Request& req,
2206     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2207     const std::string& username)
2208 {
2209     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2210     {
2211         return;
2212     }
2213     if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2214     {
2215         // If authentication is disabled, there are no user accounts
2216         messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2217         return;
2218     }
2219     std::optional<std::string> newUserName;
2220     std::optional<std::string> password;
2221     std::optional<bool> enabled;
2222     std::optional<std::string> roleId;
2223     std::optional<bool> locked;
2224     std::optional<std::vector<std::string>> accountTypes;
2225 
2226     if (req.session == nullptr)
2227     {
2228         messages::internalError(asyncResp->res);
2229         return;
2230     }
2231 
2232     bool userSelf = (username == req.session->username);
2233 
2234     Privileges effectiveUserPrivileges =
2235         redfish::getUserPrivileges(*req.session);
2236     Privileges configureUsers = {"ConfigureUsers"};
2237     bool userHasConfigureUsers =
2238         effectiveUserPrivileges.isSupersetOf(configureUsers) &&
2239         !req.session->isConfigureSelfOnly;
2240     if (userHasConfigureUsers)
2241     {
2242         // Users with ConfigureUsers can modify for all users
2243         if (!json_util::readJsonPatch(        //
2244                 req, asyncResp->res,          //
2245                 "AccountTypes", accountTypes, //
2246                 "Enabled", enabled,           //
2247                 "Locked", locked,             //
2248                 "Password", password,         //
2249                 "RoleId", roleId,             //
2250                 "UserName", newUserName       //
2251                 ))
2252         {
2253             return;
2254         }
2255     }
2256     else
2257     {
2258         // ConfigureSelf accounts can only modify their own account
2259         if (!userSelf)
2260         {
2261             messages::insufficientPrivilege(asyncResp->res);
2262             return;
2263         }
2264 
2265         // ConfigureSelf accounts can only modify their password
2266         if (!json_util::readJsonPatch(req, asyncResp->res, "Password",
2267                                       password))
2268         {
2269             return;
2270         }
2271     }
2272 
2273     // if user name is not provided in the patch method or if it
2274     // matches the user name in the URI, then we are treating it as
2275     // updating user properties other then username. If username
2276     // provided doesn't match the URI, then we are treating this as
2277     // user rename request.
2278     if (!newUserName || (newUserName.value() == username))
2279     {
2280         updateUserProperties(asyncResp, username, password, enabled, roleId,
2281                              locked, accountTypes, userSelf, req.session);
2282         return;
2283     }
2284     dbus::utility::async_method_call(
2285         asyncResp,
2286         [asyncResp, username, password(std::move(password)),
2287          roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)},
2288          locked, userSelf, session = req.session,
2289          accountTypes(std::move(accountTypes))](
2290             const boost::system::error_code& ec, sdbusplus::message_t& m) {
2291             if (ec)
2292             {
2293                 userErrorMessageHandler(m.get_error(), asyncResp, newUser,
2294                                         username);
2295                 return;
2296             }
2297 
2298             updateUserProperties(asyncResp, newUser, password, enabled, roleId,
2299                                  locked, accountTypes, userSelf, session);
2300         },
2301         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
2302         "xyz.openbmc_project.User.Manager", "RenameUser", username,
2303         *newUserName);
2304 }
2305 
2306 inline void requestAccountServiceRoutes(App& app)
2307 {
2308     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2309         .privileges(redfish::privileges::headAccountService)
2310         .methods(boost::beast::http::verb::head)(
2311             std::bind_front(handleAccountServiceHead, std::ref(app)));
2312 
2313     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2314         .privileges(redfish::privileges::getAccountService)
2315         .methods(boost::beast::http::verb::get)(
2316             std::bind_front(handleAccountServiceGet, std::ref(app)));
2317 
2318     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2319         .privileges(redfish::privileges::patchAccountService)
2320         .methods(boost::beast::http::verb::patch)(
2321             std::bind_front(handleAccountServicePatch, std::ref(app)));
2322 
2323     BMCWEB_ROUTE(
2324         app,
2325         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/")
2326         .privileges(redfish::privileges::headCertificateCollection)
2327         .methods(boost::beast::http::verb::head)(std::bind_front(
2328             handleAccountServiceClientCertificatesHead, std::ref(app)));
2329 
2330     BMCWEB_ROUTE(
2331         app,
2332         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/")
2333         .privileges(redfish::privileges::getCertificateCollection)
2334         .methods(boost::beast::http::verb::get)(std::bind_front(
2335             handleAccountServiceClientCertificatesGet, std::ref(app)));
2336 
2337     BMCWEB_ROUTE(
2338         app,
2339         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/")
2340         .privileges(redfish::privileges::headCertificate)
2341         .methods(boost::beast::http::verb::head)(std::bind_front(
2342             handleAccountServiceClientCertificatesInstanceHead, std::ref(app)));
2343 
2344     BMCWEB_ROUTE(
2345         app,
2346         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/")
2347         .privileges(redfish::privileges::getCertificate)
2348         .methods(boost::beast::http::verb::get)(std::bind_front(
2349             handleAccountServiceClientCertificatesInstanceGet, std::ref(app)));
2350 
2351     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2352         .privileges(redfish::privileges::headManagerAccountCollection)
2353         .methods(boost::beast::http::verb::head)(
2354             std::bind_front(handleAccountCollectionHead, std::ref(app)));
2355 
2356     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2357         .privileges(redfish::privileges::getManagerAccountCollection)
2358         .methods(boost::beast::http::verb::get)(
2359             std::bind_front(handleAccountCollectionGet, std::ref(app)));
2360 
2361     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2362         .privileges(redfish::privileges::postManagerAccountCollection)
2363         .methods(boost::beast::http::verb::post)(
2364             std::bind_front(handleAccountCollectionPost, std::ref(app)));
2365 
2366     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2367         .privileges(redfish::privileges::headManagerAccount)
2368         .methods(boost::beast::http::verb::head)(
2369             std::bind_front(handleAccountHead, std::ref(app)));
2370 
2371     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2372         .privileges(redfish::privileges::getManagerAccount)
2373         .methods(boost::beast::http::verb::get)(
2374             std::bind_front(handleAccountGet, std::ref(app)));
2375 
2376     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2377         // TODO this privilege should be using the generated endpoints, but
2378         // because of the special handling of ConfigureSelf, it's not able to
2379         // yet
2380         .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}})
2381         .methods(boost::beast::http::verb::patch)(
2382             std::bind_front(handleAccountPatch, std::ref(app)));
2383 
2384     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2385         .privileges(redfish::privileges::deleteManagerAccount)
2386         .methods(boost::beast::http::verb::delete_)(
2387             std::bind_front(handleAccountDelete, std::ref(app)));
2388 }
2389 
2390 } // namespace redfish
2391