xref: /openbmc/bmcweb/redfish-core/lib/account_service.hpp (revision fab7374c5be4f9ac1a6ee63b5d3627e0ceb6b170)
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     clientCertificate["Certificates"] = std::move(certificates);
1412     json["MultiFactorAuth"]["ClientCertificate"] = std::move(clientCertificate);
1413 
1414     json["Oem"]["OpenBMC"]["@odata.type"] =
1415         "#OpenBMCAccountService.v1_0_0.AccountService";
1416     json["Oem"]["OpenBMC"]["@odata.id"] =
1417         "/redfish/v1/AccountService#/Oem/OpenBMC";
1418     json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] =
1419         authMethodsConfig.basic;
1420     json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] =
1421         authMethodsConfig.sessionToken;
1422     json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken;
1423     json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie;
1424     json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls;
1425 
1426     // /redfish/v1/AccountService/LDAP/Certificates is something only
1427     // ConfigureManager can access then only display when the user has
1428     // permissions ConfigureManager
1429     Privileges effectiveUserPrivileges =
1430         redfish::getUserPrivileges(*req.session);
1431 
1432     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
1433                                          effectiveUserPrivileges))
1434     {
1435         asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] =
1436             "/redfish/v1/AccountService/LDAP/Certificates";
1437     }
1438     dbus::utility::getAllProperties(
1439         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1440         "xyz.openbmc_project.User.AccountPolicy",
1441         [asyncResp](const boost::system::error_code& ec,
1442                     const dbus::utility::DBusPropertiesMap& propertiesList) {
1443             if (ec)
1444             {
1445                 messages::internalError(asyncResp->res);
1446                 return;
1447             }
1448 
1449             BMCWEB_LOG_DEBUG("Got {} properties for AccountService",
1450                              propertiesList.size());
1451 
1452             const uint8_t* minPasswordLength = nullptr;
1453             const uint32_t* accountUnlockTimeout = nullptr;
1454             const uint16_t* maxLoginAttemptBeforeLockout = nullptr;
1455 
1456             const bool success = sdbusplus::unpackPropertiesNoThrow(
1457                 dbus_utils::UnpackErrorPrinter(), propertiesList,
1458                 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout",
1459                 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout",
1460                 maxLoginAttemptBeforeLockout);
1461 
1462             if (!success)
1463             {
1464                 messages::internalError(asyncResp->res);
1465                 return;
1466             }
1467 
1468             if (minPasswordLength != nullptr)
1469             {
1470                 asyncResp->res.jsonValue["MinPasswordLength"] =
1471                     *minPasswordLength;
1472             }
1473 
1474             if (accountUnlockTimeout != nullptr)
1475             {
1476                 asyncResp->res.jsonValue["AccountLockoutDuration"] =
1477                     *accountUnlockTimeout;
1478             }
1479 
1480             if (maxLoginAttemptBeforeLockout != nullptr)
1481             {
1482                 asyncResp->res.jsonValue["AccountLockoutThreshold"] =
1483                     *maxLoginAttemptBeforeLockout;
1484             }
1485         });
1486 
1487     auto callback = [asyncResp](bool success, const LDAPConfigData& confData,
1488                                 const std::string& ldapType) {
1489         if (!success)
1490         {
1491             return;
1492         }
1493         parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1494     };
1495 
1496     getLDAPConfigData("LDAP", callback);
1497     getLDAPConfigData("ActiveDirectory", callback);
1498 }
1499 
handleCertificateMappingAttributePatch(crow::Response & res,const std::string & certMapAttribute)1500 inline void handleCertificateMappingAttributePatch(
1501     crow::Response& res, const std::string& certMapAttribute)
1502 {
1503     MTLSCommonNameParseMode parseMode =
1504         persistent_data::getMTLSCommonNameParseMode(certMapAttribute);
1505     if (parseMode == MTLSCommonNameParseMode::Invalid)
1506     {
1507         messages::propertyValueNotInList(res, "CertificateMappingAttribute",
1508                                          certMapAttribute);
1509         return;
1510     }
1511 
1512     persistent_data::AuthConfigMethods& authMethodsConfig =
1513         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1514     authMethodsConfig.mTLSCommonNameParsingMode = parseMode;
1515 }
1516 
handleRespondToUnauthenticatedClientsPatch(App & app,const crow::Request & req,crow::Response & res,bool respondToUnauthenticatedClients)1517 inline void handleRespondToUnauthenticatedClientsPatch(
1518     App& app, const crow::Request& req, crow::Response& res,
1519     bool respondToUnauthenticatedClients)
1520 {
1521     if (req.session != nullptr)
1522     {
1523         // Sanity check.  If the user isn't currently authenticated with mutual
1524         // TLS, they very likely are about to permanently lock themselves out.
1525         // Make sure they're using mutual TLS before allowing locking.
1526         if (req.session->sessionType != persistent_data::SessionType::MutualTLS)
1527         {
1528             messages::propertyValueExternalConflict(
1529                 res,
1530                 "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients",
1531                 respondToUnauthenticatedClients);
1532             return;
1533         }
1534     }
1535 
1536     persistent_data::AuthConfigMethods& authMethodsConfig =
1537         persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1538 
1539     // Change the settings
1540     authMethodsConfig.tlsStrict = !respondToUnauthenticatedClients;
1541 
1542     // Write settings to disk
1543     persistent_data::getConfig().writeData();
1544 
1545     // Trigger a reload, to apply the new settings to new connections
1546     app.loadCertificate();
1547 }
1548 
handleAccountServicePatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1549 inline void handleAccountServicePatch(
1550     App& app, const crow::Request& req,
1551     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1552 {
1553     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1554     {
1555         return;
1556     }
1557     std::optional<uint32_t> unlockTimeout;
1558     std::optional<uint16_t> lockoutThreshold;
1559     std::optional<uint8_t> minPasswordLength;
1560     std::optional<uint16_t> maxPasswordLength;
1561     LdapPatchParams ldapObject;
1562     std::optional<std::string> certificateMappingAttribute;
1563     std::optional<bool> respondToUnauthenticatedClients;
1564     LdapPatchParams activeDirectoryObject;
1565     AuthMethods auth;
1566     std::optional<std::string> httpBasicAuth;
1567 
1568     if (!json_util::readJsonPatch(                                         //
1569             req, asyncResp->res,                                           //
1570             "AccountLockoutDuration", unlockTimeout,                       //
1571             "AccountLockoutThreshold", lockoutThreshold,                   //
1572             "ActiveDirectory/Authentication/AuthenticationType",
1573             activeDirectoryObject.authType,                                //
1574             "ActiveDirectory/Authentication/Password",
1575             activeDirectoryObject.password,                                //
1576             "ActiveDirectory/Authentication/Username",
1577             activeDirectoryObject.userName,                                //
1578             "ActiveDirectory/LDAPService/SearchSettings/BaseDistinguishedNames",
1579             activeDirectoryObject.baseDNList,                              //
1580             "ActiveDirectory/LDAPService/SearchSettings/GroupsAttribute",
1581             activeDirectoryObject.groupsAttribute,                         //
1582             "ActiveDirectory/LDAPService/SearchSettings/UsernameAttribute",
1583             activeDirectoryObject.userNameAttribute,                       //
1584             "ActiveDirectory/RemoteRoleMapping",
1585             activeDirectoryObject.remoteRoleMapData,                       //
1586             "ActiveDirectory/ServiceAddresses",
1587             activeDirectoryObject.serviceAddressList,                      //
1588             "ActiveDirectory/ServiceEnabled",
1589             activeDirectoryObject.serviceEnabled,                          //
1590             "HTTPBasicAuth", httpBasicAuth,                                //
1591             "LDAP/Authentication/AuthenticationType", ldapObject.authType, //
1592             "LDAP/Authentication/Password", ldapObject.password,           //
1593             "LDAP/Authentication/Username", ldapObject.userName,           //
1594             "LDAP/LDAPService/SearchSettings/BaseDistinguishedNames",
1595             ldapObject.baseDNList,                                         //
1596             "LDAP/LDAPService/SearchSettings/GroupsAttribute",
1597             ldapObject.groupsAttribute,                                    //
1598             "LDAP/LDAPService/SearchSettings/UsernameAttribute",
1599             ldapObject.userNameAttribute,                                  //
1600             "LDAP/RemoteRoleMapping", ldapObject.remoteRoleMapData,        //
1601             "LDAP/ServiceAddresses", ldapObject.serviceAddressList,        //
1602             "LDAP/ServiceEnabled", ldapObject.serviceEnabled,              //
1603             "MaxPasswordLength", maxPasswordLength,                        //
1604             "MinPasswordLength", minPasswordLength,                        //
1605             "MultiFactorAuth/ClientCertificate/CertificateMappingAttribute",
1606             certificateMappingAttribute,                                   //
1607             "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients",
1608             respondToUnauthenticatedClients,                               //
1609             "Oem/OpenBMC/AuthMethods/BasicAuth", auth.basicAuth,           //
1610             "Oem/OpenBMC/AuthMethods/Cookie", auth.cookie,                 //
1611             "Oem/OpenBMC/AuthMethods/SessionToken", auth.sessionToken,     //
1612             "Oem/OpenBMC/AuthMethods/TLS", auth.tls,                       //
1613             "Oem/OpenBMC/AuthMethods/XToken", auth.xToken                  //
1614             ))
1615     {
1616         return;
1617     }
1618 
1619     if (httpBasicAuth)
1620     {
1621         if (*httpBasicAuth == "Enabled")
1622         {
1623             auth.basicAuth = true;
1624         }
1625         else if (*httpBasicAuth == "Disabled")
1626         {
1627             auth.basicAuth = false;
1628         }
1629         else
1630         {
1631             messages::propertyValueNotInList(asyncResp->res, "HttpBasicAuth",
1632                                              *httpBasicAuth);
1633         }
1634     }
1635 
1636     if (respondToUnauthenticatedClients)
1637     {
1638         handleRespondToUnauthenticatedClientsPatch(
1639             app, req, asyncResp->res, *respondToUnauthenticatedClients);
1640     }
1641 
1642     if (certificateMappingAttribute)
1643     {
1644         handleCertificateMappingAttributePatch(asyncResp->res,
1645                                                *certificateMappingAttribute);
1646     }
1647 
1648     if (minPasswordLength)
1649     {
1650         setDbusProperty(
1651             asyncResp, "MinPasswordLength", "xyz.openbmc_project.User.Manager",
1652             sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1653             "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength",
1654             *minPasswordLength);
1655     }
1656 
1657     if (maxPasswordLength)
1658     {
1659         messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1660     }
1661 
1662     handleLDAPPatch(std::move(activeDirectoryObject), asyncResp,
1663                     "ActiveDirectory");
1664     handleLDAPPatch(std::move(ldapObject), asyncResp, "LDAP");
1665 
1666     handleAuthMethodsPatch(asyncResp, auth);
1667 
1668     if (unlockTimeout)
1669     {
1670         setDbusProperty(
1671             asyncResp, "AccountLockoutDuration",
1672             "xyz.openbmc_project.User.Manager",
1673             sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1674             "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout",
1675             *unlockTimeout);
1676     }
1677     if (lockoutThreshold)
1678     {
1679         setDbusProperty(
1680             asyncResp, "AccountLockoutThreshold",
1681             "xyz.openbmc_project.User.Manager",
1682             sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1683             "xyz.openbmc_project.User.AccountPolicy",
1684             "MaxLoginAttemptBeforeLockout", *lockoutThreshold);
1685     }
1686 }
1687 
handleAccountCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1688 inline void handleAccountCollectionHead(
1689     App& app, const crow::Request& req,
1690     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1691 {
1692     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1693     {
1694         return;
1695     }
1696     asyncResp->res.addHeader(
1697         boost::beast::http::field::link,
1698         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1699 }
1700 
handleAccountCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1701 inline void handleAccountCollectionGet(
1702     App& app, const crow::Request& req,
1703     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1704 {
1705     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1706     {
1707         return;
1708     }
1709 
1710     if (req.session == nullptr)
1711     {
1712         messages::internalError(asyncResp->res);
1713         return;
1714     }
1715 
1716     asyncResp->res.addHeader(
1717         boost::beast::http::field::link,
1718         "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1719 
1720     asyncResp->res.jsonValue["@odata.id"] =
1721         "/redfish/v1/AccountService/Accounts";
1722     asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection."
1723                                               "ManagerAccountCollection";
1724     asyncResp->res.jsonValue["Name"] = "Accounts Collection";
1725     asyncResp->res.jsonValue["Description"] = "BMC User Accounts";
1726 
1727     Privileges effectiveUserPrivileges =
1728         redfish::getUserPrivileges(*req.session);
1729 
1730     std::string thisUser;
1731     if (req.session)
1732     {
1733         thisUser = req.session->username;
1734     }
1735     sdbusplus::message::object_path path("/xyz/openbmc_project/user");
1736     dbus::utility::getManagedObjects(
1737         "xyz.openbmc_project.User.Manager", path,
1738         [asyncResp, thisUser, effectiveUserPrivileges](
1739             const boost::system::error_code& ec,
1740             const dbus::utility::ManagedObjectType& users) {
1741             if (ec)
1742             {
1743                 messages::internalError(asyncResp->res);
1744                 return;
1745             }
1746 
1747             bool userCanSeeAllAccounts =
1748                 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"});
1749 
1750             bool userCanSeeSelf =
1751                 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"});
1752 
1753             nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
1754             memberArray = nlohmann::json::array();
1755 
1756             for (const auto& userpath : users)
1757             {
1758                 std::string user = userpath.first.filename();
1759                 if (user.empty())
1760                 {
1761                     messages::internalError(asyncResp->res);
1762                     BMCWEB_LOG_ERROR("Invalid firmware ID");
1763 
1764                     return;
1765                 }
1766 
1767                 // As clarified by Redfish here:
1768                 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1769                 // Users without ConfigureUsers, only see their own
1770                 // account. Users with ConfigureUsers, see all
1771                 // accounts.
1772                 if (userCanSeeAllAccounts ||
1773                     (thisUser == user && userCanSeeSelf))
1774                 {
1775                     nlohmann::json::object_t member;
1776                     member["@odata.id"] = boost::urls::format(
1777                         "/redfish/v1/AccountService/Accounts/{}", user);
1778                     memberArray.emplace_back(std::move(member));
1779                 }
1780             }
1781             asyncResp->res.jsonValue["Members@odata.count"] =
1782                 memberArray.size();
1783         });
1784 }
1785 
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)1786 inline void processAfterCreateUser(
1787     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1788     const std::string& username, const std::string& password,
1789     const boost::system::error_code& ec, sdbusplus::message_t& m)
1790 {
1791     if (ec)
1792     {
1793         userErrorMessageHandler(m.get_error(), asyncResp, username, "");
1794         return;
1795     }
1796 
1797     if (pamUpdatePassword(username, password) != PAM_SUCCESS)
1798     {
1799         // At this point we have a user that's been
1800         // created, but the password set
1801         // failed.Something is wrong, so delete the user
1802         // that we've already created
1803         sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1804         tempObjPath /= username;
1805         const std::string userPath(tempObjPath);
1806 
1807         dbus::utility::async_method_call(
1808             asyncResp,
1809             [asyncResp, password](const boost::system::error_code& ec3) {
1810                 if (ec3)
1811                 {
1812                     messages::internalError(asyncResp->res);
1813                     return;
1814                 }
1815 
1816                 // If password is invalid
1817                 messages::propertyValueFormatError(asyncResp->res, nullptr,
1818                                                    "Password");
1819             },
1820             "xyz.openbmc_project.User.Manager", userPath,
1821             "xyz.openbmc_project.Object.Delete", "Delete");
1822 
1823         BMCWEB_LOG_ERROR("pamUpdatePassword Failed");
1824         return;
1825     }
1826 
1827     messages::created(asyncResp->res);
1828     asyncResp->res.addHeader("Location",
1829                              "/redfish/v1/AccountService/Accounts/" + username);
1830 }
1831 
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)1832 inline void processAfterGetAllGroups(
1833     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1834     const std::string& username, const std::string& password,
1835     const std::string& roleId, bool enabled,
1836     std::optional<std::vector<std::string>> accountTypes,
1837     const std::vector<std::string>& allGroupsList)
1838 {
1839     std::vector<std::string> userGroups;
1840     std::vector<std::string> accountTypeUserGroups;
1841 
1842     // If user specified account types then convert them to unix user groups
1843     if (accountTypes)
1844     {
1845         if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes,
1846                                          accountTypeUserGroups))
1847         {
1848             // Problem in mapping Account Types to User Groups, Error already
1849             // logged.
1850             return;
1851         }
1852     }
1853 
1854     for (const auto& grp : allGroupsList)
1855     {
1856         // If user specified the account type then only accept groups which are
1857         // in the account types group list.
1858         if (!accountTypeUserGroups.empty())
1859         {
1860             bool found = false;
1861             for (const auto& grp1 : accountTypeUserGroups)
1862             {
1863                 if (grp == grp1)
1864                 {
1865                     found = true;
1866                     break;
1867                 }
1868             }
1869             if (!found)
1870             {
1871                 continue;
1872             }
1873         }
1874 
1875         // Console access is provided to the user who is a member of
1876         // hostconsole group and has a administrator role. So, set
1877         // hostconsole group only for the administrator.
1878         if ((grp == "hostconsole") && (roleId != "priv-admin"))
1879         {
1880             if (!accountTypeUserGroups.empty())
1881             {
1882                 BMCWEB_LOG_ERROR(
1883                     "Only administrator can get HostConsole access");
1884                 asyncResp->res.result(boost::beast::http::status::bad_request);
1885                 return;
1886             }
1887             continue;
1888         }
1889         userGroups.emplace_back(grp);
1890     }
1891 
1892     // Make sure user specified groups are valid. This is internal error because
1893     // it some inconsistencies between user manager and bmcweb.
1894     if (!accountTypeUserGroups.empty() &&
1895         accountTypeUserGroups.size() != userGroups.size())
1896     {
1897         messages::internalError(asyncResp->res);
1898         return;
1899     }
1900     dbus::utility::async_method_call(
1901         asyncResp,
1902         [asyncResp, username, password](const boost::system::error_code& ec2,
1903                                         sdbusplus::message_t& m) {
1904             processAfterCreateUser(asyncResp, username, password, ec2, m);
1905         },
1906         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1907         "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups,
1908         roleId, enabled);
1909 }
1910 
handleAccountCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1911 inline void handleAccountCollectionPost(
1912     App& app, const crow::Request& req,
1913     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1914 {
1915     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1916     {
1917         return;
1918     }
1919     std::string username;
1920     std::string password;
1921     std::optional<std::string> roleIdJson;
1922     std::optional<bool> enabledJson;
1923     std::optional<std::vector<std::string>> accountTypes;
1924     if (!json_util::readJsonPatch(        //
1925             req, asyncResp->res,          //
1926             "AccountTypes", accountTypes, //
1927             "Enabled", enabledJson,       //
1928             "Password", password,         //
1929             "RoleId", roleIdJson,         //
1930             "UserName", username          //
1931             ))
1932     {
1933         return;
1934     }
1935 
1936     std::string roleId = roleIdJson.value_or("User");
1937     std::string priv = getPrivilegeFromRoleId(roleId);
1938     if (priv.empty())
1939     {
1940         messages::propertyValueNotInList(asyncResp->res, roleId, "RoleId");
1941         return;
1942     }
1943     roleId = priv;
1944 
1945     bool enabled = enabledJson.value_or(true);
1946 
1947     // Reading AllGroups property
1948     dbus::utility::getProperty<std::vector<std::string>>(
1949         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1950         "xyz.openbmc_project.User.Manager", "AllGroups",
1951         [asyncResp, username, password{std::move(password)}, roleId, enabled,
1952          accountTypes](const boost::system::error_code& ec,
1953                        const std::vector<std::string>& allGroupsList) {
1954             if (ec)
1955             {
1956                 BMCWEB_LOG_ERROR("D-Bus response error {}", ec);
1957                 messages::internalError(asyncResp->res);
1958                 return;
1959             }
1960 
1961             if (allGroupsList.empty())
1962             {
1963                 messages::internalError(asyncResp->res);
1964                 return;
1965             }
1966 
1967             processAfterGetAllGroups(asyncResp, username, password, roleId,
1968                                      enabled, accountTypes, allGroupsList);
1969         });
1970 }
1971 
handleAccountHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1972 inline void handleAccountHead(
1973     App& app, const crow::Request& req,
1974     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1975     const std::string& /*accountName*/)
1976 {
1977     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1978     {
1979         return;
1980     }
1981     asyncResp->res.addHeader(
1982         boost::beast::http::field::link,
1983         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1984 }
1985 
handleAccountGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & accountName)1986 inline void handleAccountGet(
1987     App& app, const crow::Request& req,
1988     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1989     const std::string& accountName)
1990 {
1991     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1992     {
1993         return;
1994     }
1995     asyncResp->res.addHeader(
1996         boost::beast::http::field::link,
1997         "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1998 
1999     if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2000     {
2001         // If authentication is disabled, there are no user accounts
2002         messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2003                                    accountName);
2004         return;
2005     }
2006 
2007     if (req.session == nullptr)
2008     {
2009         messages::internalError(asyncResp->res);
2010         return;
2011     }
2012     if (req.session->username != accountName)
2013     {
2014         // At this point we've determined that the user is trying to
2015         // modify a user that isn't them.  We need to verify that they
2016         // have permissions to modify other users, so re-run the auth
2017         // check with the same permissions, minus ConfigureSelf.
2018         Privileges effectiveUserPrivileges =
2019             redfish::getUserPrivileges(*req.session);
2020         Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers",
2021                                                          "ConfigureManager"};
2022         if (!effectiveUserPrivileges.isSupersetOf(
2023                 requiredPermissionsToChangeNonSelf))
2024         {
2025             BMCWEB_LOG_DEBUG("GET Account denied access");
2026             messages::insufficientPrivilege(asyncResp->res);
2027             return;
2028         }
2029     }
2030 
2031     sdbusplus::message::object_path path("/xyz/openbmc_project/user");
2032     dbus::utility::getManagedObjects(
2033         "xyz.openbmc_project.User.Manager", path,
2034         [asyncResp,
2035          accountName](const boost::system::error_code& ec,
2036                       const dbus::utility::ManagedObjectType& users) {
2037             if (ec)
2038             {
2039                 messages::internalError(asyncResp->res);
2040                 return;
2041             }
2042             const auto userIt = std::ranges::find_if(
2043                 users,
2044                 [accountName](
2045                     const std::pair<sdbusplus::message::object_path,
2046                                     dbus::utility::DBusInterfacesMap>& user) {
2047                     return accountName == user.first.filename();
2048                 });
2049 
2050             if (userIt == users.end())
2051             {
2052                 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2053                                            accountName);
2054                 return;
2055             }
2056 
2057             asyncResp->res.jsonValue["@odata.type"] =
2058                 "#ManagerAccount.v1_7_0.ManagerAccount";
2059             asyncResp->res.jsonValue["Name"] = "User Account";
2060             asyncResp->res.jsonValue["Description"] = "User Account";
2061             asyncResp->res.jsonValue["Password"] = nullptr;
2062             asyncResp->res.jsonValue["StrictAccountTypes"] = true;
2063 
2064             for (const auto& interface : userIt->second)
2065             {
2066                 if (interface.first == "xyz.openbmc_project.User.Attributes")
2067                 {
2068                     const bool* userEnabled = nullptr;
2069                     const bool* userLocked = nullptr;
2070                     const std::string* userPrivPtr = nullptr;
2071                     const bool* userPasswordExpired = nullptr;
2072                     const std::vector<std::string>* userGroups = nullptr;
2073 
2074                     const bool success = sdbusplus::unpackPropertiesNoThrow(
2075                         dbus_utils::UnpackErrorPrinter(), interface.second,
2076                         "UserEnabled", userEnabled,
2077                         "UserLockedForFailedAttempt", userLocked,
2078                         "UserPrivilege", userPrivPtr, "UserPasswordExpired",
2079                         userPasswordExpired, "UserGroups", userGroups);
2080                     if (!success)
2081                     {
2082                         messages::internalError(asyncResp->res);
2083                         return;
2084                     }
2085                     if (userEnabled == nullptr)
2086                     {
2087                         BMCWEB_LOG_ERROR("UserEnabled wasn't a bool");
2088                         messages::internalError(asyncResp->res);
2089                         return;
2090                     }
2091                     asyncResp->res.jsonValue["Enabled"] = *userEnabled;
2092 
2093                     if (userLocked == nullptr)
2094                     {
2095                         BMCWEB_LOG_ERROR("UserLockedForF"
2096                                          "ailedAttempt "
2097                                          "wasn't a bool");
2098                         messages::internalError(asyncResp->res);
2099                         return;
2100                     }
2101                     asyncResp->res.jsonValue["Locked"] = *userLocked;
2102                     nlohmann::json::array_t allowed;
2103                     // can only unlock accounts
2104                     allowed.emplace_back("false");
2105                     asyncResp->res.jsonValue["Locked@Redfish.AllowableValues"] =
2106                         std::move(allowed);
2107 
2108                     if (userPrivPtr == nullptr)
2109                     {
2110                         BMCWEB_LOG_ERROR("UserPrivilege wasn't a "
2111                                          "string");
2112                         messages::internalError(asyncResp->res);
2113                         return;
2114                     }
2115                     std::string role = getRoleIdFromPrivilege(*userPrivPtr);
2116                     if (role.empty())
2117                     {
2118                         BMCWEB_LOG_ERROR("Invalid user role");
2119                         messages::internalError(asyncResp->res);
2120                         return;
2121                     }
2122                     asyncResp->res.jsonValue["RoleId"] = role;
2123 
2124                     nlohmann::json& roleEntry =
2125                         asyncResp->res.jsonValue["Links"]["Role"];
2126                     roleEntry["@odata.id"] = boost::urls::format(
2127                         "/redfish/v1/AccountService/Roles/{}", role);
2128 
2129                     if (userPasswordExpired == nullptr)
2130                     {
2131                         BMCWEB_LOG_ERROR("UserPasswordExpired wasn't a bool");
2132                         messages::internalError(asyncResp->res);
2133                         return;
2134                     }
2135                     asyncResp->res.jsonValue["PasswordChangeRequired"] =
2136                         *userPasswordExpired;
2137 
2138                     if (userGroups == nullptr)
2139                     {
2140                         BMCWEB_LOG_ERROR("userGroups wasn't a string vector");
2141                         messages::internalError(asyncResp->res);
2142                         return;
2143                     }
2144                     if (!translateUserGroup(*userGroups, asyncResp->res))
2145                     {
2146                         BMCWEB_LOG_ERROR("userGroups mapping failed");
2147                         messages::internalError(asyncResp->res);
2148                         return;
2149                     }
2150                 }
2151             }
2152 
2153             asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2154                 "/redfish/v1/AccountService/Accounts/{}", accountName);
2155             asyncResp->res.jsonValue["Id"] = accountName;
2156             asyncResp->res.jsonValue["UserName"] = accountName;
2157         });
2158 }
2159 
handleAccountDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & username)2160 inline void handleAccountDelete(
2161     App& app, const crow::Request& req,
2162     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2163     const std::string& username)
2164 {
2165     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2166     {
2167         return;
2168     }
2169 
2170     if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2171     {
2172         // If authentication is disabled, there are no user accounts
2173         messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2174         return;
2175     }
2176     sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
2177     tempObjPath /= username;
2178     const std::string userPath(tempObjPath);
2179 
2180     dbus::utility::async_method_call(
2181         asyncResp,
2182         [asyncResp, username](const boost::system::error_code& ec) {
2183             if (ec)
2184             {
2185                 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2186                                            username);
2187                 return;
2188             }
2189 
2190             messages::accountRemoved(asyncResp->res);
2191         },
2192         "xyz.openbmc_project.User.Manager", userPath,
2193         "xyz.openbmc_project.Object.Delete", "Delete");
2194 }
2195 
handleAccountPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & username)2196 inline void handleAccountPatch(
2197     App& app, const crow::Request& req,
2198     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2199     const std::string& username)
2200 {
2201     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2202     {
2203         return;
2204     }
2205     if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2206     {
2207         // If authentication is disabled, there are no user accounts
2208         messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2209         return;
2210     }
2211     std::optional<std::string> newUserName;
2212     std::optional<std::string> password;
2213     std::optional<bool> enabled;
2214     std::optional<std::string> roleId;
2215     std::optional<bool> locked;
2216     std::optional<std::vector<std::string>> accountTypes;
2217 
2218     if (req.session == nullptr)
2219     {
2220         messages::internalError(asyncResp->res);
2221         return;
2222     }
2223 
2224     bool userSelf = (username == req.session->username);
2225 
2226     Privileges effectiveUserPrivileges =
2227         redfish::getUserPrivileges(*req.session);
2228     Privileges configureUsers = {"ConfigureUsers"};
2229     bool userHasConfigureUsers =
2230         effectiveUserPrivileges.isSupersetOf(configureUsers) &&
2231         !req.session->isConfigureSelfOnly;
2232     if (userHasConfigureUsers)
2233     {
2234         // Users with ConfigureUsers can modify for all users
2235         if (!json_util::readJsonPatch(        //
2236                 req, asyncResp->res,          //
2237                 "AccountTypes", accountTypes, //
2238                 "Enabled", enabled,           //
2239                 "Locked", locked,             //
2240                 "Password", password,         //
2241                 "RoleId", roleId,             //
2242                 "UserName", newUserName       //
2243                 ))
2244         {
2245             return;
2246         }
2247     }
2248     else
2249     {
2250         // ConfigureSelf accounts can only modify their own account
2251         if (!userSelf)
2252         {
2253             messages::insufficientPrivilege(asyncResp->res);
2254             return;
2255         }
2256 
2257         // ConfigureSelf accounts can only modify their password
2258         if (!json_util::readJsonPatch(req, asyncResp->res, "Password",
2259                                       password))
2260         {
2261             return;
2262         }
2263     }
2264 
2265     // if user name is not provided in the patch method or if it
2266     // matches the user name in the URI, then we are treating it as
2267     // updating user properties other then username. If username
2268     // provided doesn't match the URI, then we are treating this as
2269     // user rename request.
2270     if (!newUserName || (newUserName.value() == username))
2271     {
2272         updateUserProperties(asyncResp, username, password, enabled, roleId,
2273                              locked, accountTypes, userSelf, req.session);
2274         return;
2275     }
2276     dbus::utility::async_method_call(
2277         asyncResp,
2278         [asyncResp, username, password(std::move(password)),
2279          roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)},
2280          locked, userSelf, session = req.session,
2281          accountTypes(std::move(accountTypes))](
2282             const boost::system::error_code& ec, sdbusplus::message_t& m) {
2283             if (ec)
2284             {
2285                 userErrorMessageHandler(m.get_error(), asyncResp, newUser,
2286                                         username);
2287                 return;
2288             }
2289 
2290             updateUserProperties(asyncResp, newUser, password, enabled, roleId,
2291                                  locked, accountTypes, userSelf, session);
2292         },
2293         "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
2294         "xyz.openbmc_project.User.Manager", "RenameUser", username,
2295         *newUserName);
2296 }
2297 
requestAccountServiceRoutes(App & app)2298 inline void requestAccountServiceRoutes(App& app)
2299 {
2300     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2301         .privileges(redfish::privileges::headAccountService)
2302         .methods(boost::beast::http::verb::head)(
2303             std::bind_front(handleAccountServiceHead, std::ref(app)));
2304 
2305     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2306         .privileges(redfish::privileges::getAccountService)
2307         .methods(boost::beast::http::verb::get)(
2308             std::bind_front(handleAccountServiceGet, std::ref(app)));
2309 
2310     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2311         .privileges(redfish::privileges::patchAccountService)
2312         .methods(boost::beast::http::verb::patch)(
2313             std::bind_front(handleAccountServicePatch, std::ref(app)));
2314 
2315     BMCWEB_ROUTE(
2316         app,
2317         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/")
2318         .privileges(redfish::privileges::headCertificateCollection)
2319         .methods(boost::beast::http::verb::head)(std::bind_front(
2320             handleAccountServiceClientCertificatesHead, std::ref(app)));
2321 
2322     BMCWEB_ROUTE(
2323         app,
2324         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/")
2325         .privileges(redfish::privileges::getCertificateCollection)
2326         .methods(boost::beast::http::verb::get)(std::bind_front(
2327             handleAccountServiceClientCertificatesGet, std::ref(app)));
2328 
2329     BMCWEB_ROUTE(
2330         app,
2331         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/")
2332         .privileges(redfish::privileges::headCertificate)
2333         .methods(boost::beast::http::verb::head)(std::bind_front(
2334             handleAccountServiceClientCertificatesInstanceHead, std::ref(app)));
2335 
2336     BMCWEB_ROUTE(
2337         app,
2338         "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/")
2339         .privileges(redfish::privileges::getCertificate)
2340         .methods(boost::beast::http::verb::get)(std::bind_front(
2341             handleAccountServiceClientCertificatesInstanceGet, std::ref(app)));
2342 
2343     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2344         .privileges(redfish::privileges::headManagerAccountCollection)
2345         .methods(boost::beast::http::verb::head)(
2346             std::bind_front(handleAccountCollectionHead, std::ref(app)));
2347 
2348     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2349         .privileges(redfish::privileges::getManagerAccountCollection)
2350         .methods(boost::beast::http::verb::get)(
2351             std::bind_front(handleAccountCollectionGet, std::ref(app)));
2352 
2353     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2354         .privileges(redfish::privileges::postManagerAccountCollection)
2355         .methods(boost::beast::http::verb::post)(
2356             std::bind_front(handleAccountCollectionPost, std::ref(app)));
2357 
2358     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2359         .privileges(redfish::privileges::headManagerAccount)
2360         .methods(boost::beast::http::verb::head)(
2361             std::bind_front(handleAccountHead, std::ref(app)));
2362 
2363     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2364         .privileges(redfish::privileges::getManagerAccount)
2365         .methods(boost::beast::http::verb::get)(
2366             std::bind_front(handleAccountGet, std::ref(app)));
2367 
2368     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2369         // TODO this privilege should be using the generated endpoints, but
2370         // because of the special handling of ConfigureSelf, it's not able to
2371         // yet
2372         .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}})
2373         .methods(boost::beast::http::verb::patch)(
2374             std::bind_front(handleAccountPatch, std::ref(app)));
2375 
2376     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2377         .privileges(redfish::privileges::deleteManagerAccount)
2378         .methods(boost::beast::http::verb::delete_)(
2379             std::bind_front(handleAccountDelete, std::ref(app)));
2380 }
2381 
2382 } // namespace redfish
2383