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