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