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