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