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