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