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