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