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