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