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