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