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