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