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