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