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