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