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