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