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