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