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