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