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