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 clientCertificate["Certificates"] = std::move(certificates);
1412 json["MultiFactorAuth"]["ClientCertificate"] = std::move(clientCertificate);
1413
1414 json["Oem"]["OpenBMC"]["@odata.type"] =
1415 "#OpenBMCAccountService.v1_0_0.AccountService";
1416 json["Oem"]["OpenBMC"]["@odata.id"] =
1417 "/redfish/v1/AccountService#/Oem/OpenBMC";
1418 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] =
1419 authMethodsConfig.basic;
1420 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] =
1421 authMethodsConfig.sessionToken;
1422 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken;
1423 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie;
1424 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls;
1425
1426 // /redfish/v1/AccountService/LDAP/Certificates is something only
1427 // ConfigureManager can access then only display when the user has
1428 // permissions ConfigureManager
1429 Privileges effectiveUserPrivileges =
1430 redfish::getUserPrivileges(*req.session);
1431
1432 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
1433 effectiveUserPrivileges))
1434 {
1435 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] =
1436 "/redfish/v1/AccountService/LDAP/Certificates";
1437 }
1438 dbus::utility::getAllProperties(
1439 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1440 "xyz.openbmc_project.User.AccountPolicy",
1441 [asyncResp](const boost::system::error_code& ec,
1442 const dbus::utility::DBusPropertiesMap& propertiesList) {
1443 if (ec)
1444 {
1445 messages::internalError(asyncResp->res);
1446 return;
1447 }
1448
1449 BMCWEB_LOG_DEBUG("Got {} properties for AccountService",
1450 propertiesList.size());
1451
1452 const uint8_t* minPasswordLength = nullptr;
1453 const uint32_t* accountUnlockTimeout = nullptr;
1454 const uint16_t* maxLoginAttemptBeforeLockout = nullptr;
1455
1456 const bool success = sdbusplus::unpackPropertiesNoThrow(
1457 dbus_utils::UnpackErrorPrinter(), propertiesList,
1458 "MinPasswordLength", minPasswordLength, "AccountUnlockTimeout",
1459 accountUnlockTimeout, "MaxLoginAttemptBeforeLockout",
1460 maxLoginAttemptBeforeLockout);
1461
1462 if (!success)
1463 {
1464 messages::internalError(asyncResp->res);
1465 return;
1466 }
1467
1468 if (minPasswordLength != nullptr)
1469 {
1470 asyncResp->res.jsonValue["MinPasswordLength"] =
1471 *minPasswordLength;
1472 }
1473
1474 if (accountUnlockTimeout != nullptr)
1475 {
1476 asyncResp->res.jsonValue["AccountLockoutDuration"] =
1477 *accountUnlockTimeout;
1478 }
1479
1480 if (maxLoginAttemptBeforeLockout != nullptr)
1481 {
1482 asyncResp->res.jsonValue["AccountLockoutThreshold"] =
1483 *maxLoginAttemptBeforeLockout;
1484 }
1485 });
1486
1487 auto callback = [asyncResp](bool success, const LDAPConfigData& confData,
1488 const std::string& ldapType) {
1489 if (!success)
1490 {
1491 return;
1492 }
1493 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType);
1494 };
1495
1496 getLDAPConfigData("LDAP", callback);
1497 getLDAPConfigData("ActiveDirectory", callback);
1498 }
1499
handleCertificateMappingAttributePatch(crow::Response & res,const std::string & certMapAttribute)1500 inline void handleCertificateMappingAttributePatch(
1501 crow::Response& res, const std::string& certMapAttribute)
1502 {
1503 MTLSCommonNameParseMode parseMode =
1504 persistent_data::getMTLSCommonNameParseMode(certMapAttribute);
1505 if (parseMode == MTLSCommonNameParseMode::Invalid)
1506 {
1507 messages::propertyValueNotInList(res, "CertificateMappingAttribute",
1508 certMapAttribute);
1509 return;
1510 }
1511
1512 persistent_data::AuthConfigMethods& authMethodsConfig =
1513 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1514 authMethodsConfig.mTLSCommonNameParsingMode = parseMode;
1515 }
1516
handleRespondToUnauthenticatedClientsPatch(App & app,const crow::Request & req,crow::Response & res,bool respondToUnauthenticatedClients)1517 inline void handleRespondToUnauthenticatedClientsPatch(
1518 App& app, const crow::Request& req, crow::Response& res,
1519 bool respondToUnauthenticatedClients)
1520 {
1521 if (req.session != nullptr)
1522 {
1523 // Sanity check. If the user isn't currently authenticated with mutual
1524 // TLS, they very likely are about to permanently lock themselves out.
1525 // Make sure they're using mutual TLS before allowing locking.
1526 if (req.session->sessionType != persistent_data::SessionType::MutualTLS)
1527 {
1528 messages::propertyValueExternalConflict(
1529 res,
1530 "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients",
1531 respondToUnauthenticatedClients);
1532 return;
1533 }
1534 }
1535
1536 persistent_data::AuthConfigMethods& authMethodsConfig =
1537 persistent_data::SessionStore::getInstance().getAuthMethodsConfig();
1538
1539 // Change the settings
1540 authMethodsConfig.tlsStrict = !respondToUnauthenticatedClients;
1541
1542 // Write settings to disk
1543 persistent_data::getConfig().writeData();
1544
1545 // Trigger a reload, to apply the new settings to new connections
1546 app.loadCertificate();
1547 }
1548
handleAccountServicePatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1549 inline void handleAccountServicePatch(
1550 App& app, const crow::Request& req,
1551 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1552 {
1553 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1554 {
1555 return;
1556 }
1557 std::optional<uint32_t> unlockTimeout;
1558 std::optional<uint16_t> lockoutThreshold;
1559 std::optional<uint8_t> minPasswordLength;
1560 std::optional<uint16_t> maxPasswordLength;
1561 LdapPatchParams ldapObject;
1562 std::optional<std::string> certificateMappingAttribute;
1563 std::optional<bool> respondToUnauthenticatedClients;
1564 LdapPatchParams activeDirectoryObject;
1565 AuthMethods auth;
1566 std::optional<std::string> httpBasicAuth;
1567
1568 if (!json_util::readJsonPatch( //
1569 req, asyncResp->res, //
1570 "AccountLockoutDuration", unlockTimeout, //
1571 "AccountLockoutThreshold", lockoutThreshold, //
1572 "ActiveDirectory/Authentication/AuthenticationType",
1573 activeDirectoryObject.authType, //
1574 "ActiveDirectory/Authentication/Password",
1575 activeDirectoryObject.password, //
1576 "ActiveDirectory/Authentication/Username",
1577 activeDirectoryObject.userName, //
1578 "ActiveDirectory/LDAPService/SearchSettings/BaseDistinguishedNames",
1579 activeDirectoryObject.baseDNList, //
1580 "ActiveDirectory/LDAPService/SearchSettings/GroupsAttribute",
1581 activeDirectoryObject.groupsAttribute, //
1582 "ActiveDirectory/LDAPService/SearchSettings/UsernameAttribute",
1583 activeDirectoryObject.userNameAttribute, //
1584 "ActiveDirectory/RemoteRoleMapping",
1585 activeDirectoryObject.remoteRoleMapData, //
1586 "ActiveDirectory/ServiceAddresses",
1587 activeDirectoryObject.serviceAddressList, //
1588 "ActiveDirectory/ServiceEnabled",
1589 activeDirectoryObject.serviceEnabled, //
1590 "HTTPBasicAuth", httpBasicAuth, //
1591 "LDAP/Authentication/AuthenticationType", ldapObject.authType, //
1592 "LDAP/Authentication/Password", ldapObject.password, //
1593 "LDAP/Authentication/Username", ldapObject.userName, //
1594 "LDAP/LDAPService/SearchSettings/BaseDistinguishedNames",
1595 ldapObject.baseDNList, //
1596 "LDAP/LDAPService/SearchSettings/GroupsAttribute",
1597 ldapObject.groupsAttribute, //
1598 "LDAP/LDAPService/SearchSettings/UsernameAttribute",
1599 ldapObject.userNameAttribute, //
1600 "LDAP/RemoteRoleMapping", ldapObject.remoteRoleMapData, //
1601 "LDAP/ServiceAddresses", ldapObject.serviceAddressList, //
1602 "LDAP/ServiceEnabled", ldapObject.serviceEnabled, //
1603 "MaxPasswordLength", maxPasswordLength, //
1604 "MinPasswordLength", minPasswordLength, //
1605 "MultiFactorAuth/ClientCertificate/CertificateMappingAttribute",
1606 certificateMappingAttribute, //
1607 "MultiFactorAuth/ClientCertificate/RespondToUnauthenticatedClients",
1608 respondToUnauthenticatedClients, //
1609 "Oem/OpenBMC/AuthMethods/BasicAuth", auth.basicAuth, //
1610 "Oem/OpenBMC/AuthMethods/Cookie", auth.cookie, //
1611 "Oem/OpenBMC/AuthMethods/SessionToken", auth.sessionToken, //
1612 "Oem/OpenBMC/AuthMethods/TLS", auth.tls, //
1613 "Oem/OpenBMC/AuthMethods/XToken", auth.xToken //
1614 ))
1615 {
1616 return;
1617 }
1618
1619 if (httpBasicAuth)
1620 {
1621 if (*httpBasicAuth == "Enabled")
1622 {
1623 auth.basicAuth = true;
1624 }
1625 else if (*httpBasicAuth == "Disabled")
1626 {
1627 auth.basicAuth = false;
1628 }
1629 else
1630 {
1631 messages::propertyValueNotInList(asyncResp->res, "HttpBasicAuth",
1632 *httpBasicAuth);
1633 }
1634 }
1635
1636 if (respondToUnauthenticatedClients)
1637 {
1638 handleRespondToUnauthenticatedClientsPatch(
1639 app, req, asyncResp->res, *respondToUnauthenticatedClients);
1640 }
1641
1642 if (certificateMappingAttribute)
1643 {
1644 handleCertificateMappingAttributePatch(asyncResp->res,
1645 *certificateMappingAttribute);
1646 }
1647
1648 if (minPasswordLength)
1649 {
1650 setDbusProperty(
1651 asyncResp, "MinPasswordLength", "xyz.openbmc_project.User.Manager",
1652 sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1653 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength",
1654 *minPasswordLength);
1655 }
1656
1657 if (maxPasswordLength)
1658 {
1659 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength");
1660 }
1661
1662 handleLDAPPatch(std::move(activeDirectoryObject), asyncResp,
1663 "ActiveDirectory");
1664 handleLDAPPatch(std::move(ldapObject), asyncResp, "LDAP");
1665
1666 handleAuthMethodsPatch(asyncResp, auth);
1667
1668 if (unlockTimeout)
1669 {
1670 setDbusProperty(
1671 asyncResp, "AccountLockoutDuration",
1672 "xyz.openbmc_project.User.Manager",
1673 sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1674 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout",
1675 *unlockTimeout);
1676 }
1677 if (lockoutThreshold)
1678 {
1679 setDbusProperty(
1680 asyncResp, "AccountLockoutThreshold",
1681 "xyz.openbmc_project.User.Manager",
1682 sdbusplus::message::object_path("/xyz/openbmc_project/user"),
1683 "xyz.openbmc_project.User.AccountPolicy",
1684 "MaxLoginAttemptBeforeLockout", *lockoutThreshold);
1685 }
1686 }
1687
handleAccountCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1688 inline void handleAccountCollectionHead(
1689 App& app, const crow::Request& req,
1690 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1691 {
1692 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1693 {
1694 return;
1695 }
1696 asyncResp->res.addHeader(
1697 boost::beast::http::field::link,
1698 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1699 }
1700
handleAccountCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1701 inline void handleAccountCollectionGet(
1702 App& app, const crow::Request& req,
1703 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1704 {
1705 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1706 {
1707 return;
1708 }
1709
1710 if (req.session == nullptr)
1711 {
1712 messages::internalError(asyncResp->res);
1713 return;
1714 }
1715
1716 asyncResp->res.addHeader(
1717 boost::beast::http::field::link,
1718 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby");
1719
1720 asyncResp->res.jsonValue["@odata.id"] =
1721 "/redfish/v1/AccountService/Accounts";
1722 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection."
1723 "ManagerAccountCollection";
1724 asyncResp->res.jsonValue["Name"] = "Accounts Collection";
1725 asyncResp->res.jsonValue["Description"] = "BMC User Accounts";
1726
1727 Privileges effectiveUserPrivileges =
1728 redfish::getUserPrivileges(*req.session);
1729
1730 std::string thisUser;
1731 if (req.session)
1732 {
1733 thisUser = req.session->username;
1734 }
1735 sdbusplus::message::object_path path("/xyz/openbmc_project/user");
1736 dbus::utility::getManagedObjects(
1737 "xyz.openbmc_project.User.Manager", path,
1738 [asyncResp, thisUser, effectiveUserPrivileges](
1739 const boost::system::error_code& ec,
1740 const dbus::utility::ManagedObjectType& users) {
1741 if (ec)
1742 {
1743 messages::internalError(asyncResp->res);
1744 return;
1745 }
1746
1747 bool userCanSeeAllAccounts =
1748 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"});
1749
1750 bool userCanSeeSelf =
1751 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"});
1752
1753 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"];
1754 memberArray = nlohmann::json::array();
1755
1756 for (const auto& userpath : users)
1757 {
1758 std::string user = userpath.first.filename();
1759 if (user.empty())
1760 {
1761 messages::internalError(asyncResp->res);
1762 BMCWEB_LOG_ERROR("Invalid firmware ID");
1763
1764 return;
1765 }
1766
1767 // As clarified by Redfish here:
1768 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration
1769 // Users without ConfigureUsers, only see their own
1770 // account. Users with ConfigureUsers, see all
1771 // accounts.
1772 if (userCanSeeAllAccounts ||
1773 (thisUser == user && userCanSeeSelf))
1774 {
1775 nlohmann::json::object_t member;
1776 member["@odata.id"] = boost::urls::format(
1777 "/redfish/v1/AccountService/Accounts/{}", user);
1778 memberArray.emplace_back(std::move(member));
1779 }
1780 }
1781 asyncResp->res.jsonValue["Members@odata.count"] =
1782 memberArray.size();
1783 });
1784 }
1785
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)1786 inline void processAfterCreateUser(
1787 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1788 const std::string& username, const std::string& password,
1789 const boost::system::error_code& ec, sdbusplus::message_t& m)
1790 {
1791 if (ec)
1792 {
1793 userErrorMessageHandler(m.get_error(), asyncResp, username, "");
1794 return;
1795 }
1796
1797 if (pamUpdatePassword(username, password) != PAM_SUCCESS)
1798 {
1799 // At this point we have a user that's been
1800 // created, but the password set
1801 // failed.Something is wrong, so delete the user
1802 // that we've already created
1803 sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
1804 tempObjPath /= username;
1805 const std::string userPath(tempObjPath);
1806
1807 dbus::utility::async_method_call(
1808 asyncResp,
1809 [asyncResp, password](const boost::system::error_code& ec3) {
1810 if (ec3)
1811 {
1812 messages::internalError(asyncResp->res);
1813 return;
1814 }
1815
1816 // If password is invalid
1817 messages::propertyValueFormatError(asyncResp->res, nullptr,
1818 "Password");
1819 },
1820 "xyz.openbmc_project.User.Manager", userPath,
1821 "xyz.openbmc_project.Object.Delete", "Delete");
1822
1823 BMCWEB_LOG_ERROR("pamUpdatePassword Failed");
1824 return;
1825 }
1826
1827 messages::created(asyncResp->res);
1828 asyncResp->res.addHeader("Location",
1829 "/redfish/v1/AccountService/Accounts/" + username);
1830 }
1831
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)1832 inline void processAfterGetAllGroups(
1833 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1834 const std::string& username, const std::string& password,
1835 const std::string& roleId, bool enabled,
1836 std::optional<std::vector<std::string>> accountTypes,
1837 const std::vector<std::string>& allGroupsList)
1838 {
1839 std::vector<std::string> userGroups;
1840 std::vector<std::string> accountTypeUserGroups;
1841
1842 // If user specified account types then convert them to unix user groups
1843 if (accountTypes)
1844 {
1845 if (!getUserGroupFromAccountType(asyncResp->res, *accountTypes,
1846 accountTypeUserGroups))
1847 {
1848 // Problem in mapping Account Types to User Groups, Error already
1849 // logged.
1850 return;
1851 }
1852 }
1853
1854 for (const auto& grp : allGroupsList)
1855 {
1856 // If user specified the account type then only accept groups which are
1857 // in the account types group list.
1858 if (!accountTypeUserGroups.empty())
1859 {
1860 bool found = false;
1861 for (const auto& grp1 : accountTypeUserGroups)
1862 {
1863 if (grp == grp1)
1864 {
1865 found = true;
1866 break;
1867 }
1868 }
1869 if (!found)
1870 {
1871 continue;
1872 }
1873 }
1874
1875 // Console access is provided to the user who is a member of
1876 // hostconsole group and has a administrator role. So, set
1877 // hostconsole group only for the administrator.
1878 if ((grp == "hostconsole") && (roleId != "priv-admin"))
1879 {
1880 if (!accountTypeUserGroups.empty())
1881 {
1882 BMCWEB_LOG_ERROR(
1883 "Only administrator can get HostConsole access");
1884 asyncResp->res.result(boost::beast::http::status::bad_request);
1885 return;
1886 }
1887 continue;
1888 }
1889 userGroups.emplace_back(grp);
1890 }
1891
1892 // Make sure user specified groups are valid. This is internal error because
1893 // it some inconsistencies between user manager and bmcweb.
1894 if (!accountTypeUserGroups.empty() &&
1895 accountTypeUserGroups.size() != userGroups.size())
1896 {
1897 messages::internalError(asyncResp->res);
1898 return;
1899 }
1900 dbus::utility::async_method_call(
1901 asyncResp,
1902 [asyncResp, username, password](const boost::system::error_code& ec2,
1903 sdbusplus::message_t& m) {
1904 processAfterCreateUser(asyncResp, username, password, ec2, m);
1905 },
1906 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1907 "xyz.openbmc_project.User.Manager", "CreateUser", username, userGroups,
1908 roleId, enabled);
1909 }
1910
handleAccountCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1911 inline void handleAccountCollectionPost(
1912 App& app, const crow::Request& req,
1913 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1914 {
1915 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1916 {
1917 return;
1918 }
1919 std::string username;
1920 std::string password;
1921 std::optional<std::string> roleIdJson;
1922 std::optional<bool> enabledJson;
1923 std::optional<std::vector<std::string>> accountTypes;
1924 if (!json_util::readJsonPatch( //
1925 req, asyncResp->res, //
1926 "AccountTypes", accountTypes, //
1927 "Enabled", enabledJson, //
1928 "Password", password, //
1929 "RoleId", roleIdJson, //
1930 "UserName", username //
1931 ))
1932 {
1933 return;
1934 }
1935
1936 std::string roleId = roleIdJson.value_or("User");
1937 std::string priv = getPrivilegeFromRoleId(roleId);
1938 if (priv.empty())
1939 {
1940 messages::propertyValueNotInList(asyncResp->res, roleId, "RoleId");
1941 return;
1942 }
1943 roleId = priv;
1944
1945 bool enabled = enabledJson.value_or(true);
1946
1947 // Reading AllGroups property
1948 dbus::utility::getProperty<std::vector<std::string>>(
1949 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
1950 "xyz.openbmc_project.User.Manager", "AllGroups",
1951 [asyncResp, username, password{std::move(password)}, roleId, enabled,
1952 accountTypes](const boost::system::error_code& ec,
1953 const std::vector<std::string>& allGroupsList) {
1954 if (ec)
1955 {
1956 BMCWEB_LOG_ERROR("D-Bus response error {}", ec);
1957 messages::internalError(asyncResp->res);
1958 return;
1959 }
1960
1961 if (allGroupsList.empty())
1962 {
1963 messages::internalError(asyncResp->res);
1964 return;
1965 }
1966
1967 processAfterGetAllGroups(asyncResp, username, password, roleId,
1968 enabled, accountTypes, allGroupsList);
1969 });
1970 }
1971
handleAccountHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string &)1972 inline void handleAccountHead(
1973 App& app, const crow::Request& req,
1974 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1975 const std::string& /*accountName*/)
1976 {
1977 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1978 {
1979 return;
1980 }
1981 asyncResp->res.addHeader(
1982 boost::beast::http::field::link,
1983 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1984 }
1985
handleAccountGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & accountName)1986 inline void handleAccountGet(
1987 App& app, const crow::Request& req,
1988 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1989 const std::string& accountName)
1990 {
1991 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1992 {
1993 return;
1994 }
1995 asyncResp->res.addHeader(
1996 boost::beast::http::field::link,
1997 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby");
1998
1999 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2000 {
2001 // If authentication is disabled, there are no user accounts
2002 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2003 accountName);
2004 return;
2005 }
2006
2007 if (req.session == nullptr)
2008 {
2009 messages::internalError(asyncResp->res);
2010 return;
2011 }
2012 if (req.session->username != accountName)
2013 {
2014 // At this point we've determined that the user is trying to
2015 // modify a user that isn't them. We need to verify that they
2016 // have permissions to modify other users, so re-run the auth
2017 // check with the same permissions, minus ConfigureSelf.
2018 Privileges effectiveUserPrivileges =
2019 redfish::getUserPrivileges(*req.session);
2020 Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers",
2021 "ConfigureManager"};
2022 if (!effectiveUserPrivileges.isSupersetOf(
2023 requiredPermissionsToChangeNonSelf))
2024 {
2025 BMCWEB_LOG_DEBUG("GET Account denied access");
2026 messages::insufficientPrivilege(asyncResp->res);
2027 return;
2028 }
2029 }
2030
2031 sdbusplus::message::object_path path("/xyz/openbmc_project/user");
2032 dbus::utility::getManagedObjects(
2033 "xyz.openbmc_project.User.Manager", path,
2034 [asyncResp,
2035 accountName](const boost::system::error_code& ec,
2036 const dbus::utility::ManagedObjectType& users) {
2037 if (ec)
2038 {
2039 messages::internalError(asyncResp->res);
2040 return;
2041 }
2042 const auto userIt = std::ranges::find_if(
2043 users,
2044 [accountName](
2045 const std::pair<sdbusplus::message::object_path,
2046 dbus::utility::DBusInterfacesMap>& user) {
2047 return accountName == user.first.filename();
2048 });
2049
2050 if (userIt == users.end())
2051 {
2052 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2053 accountName);
2054 return;
2055 }
2056
2057 asyncResp->res.jsonValue["@odata.type"] =
2058 "#ManagerAccount.v1_7_0.ManagerAccount";
2059 asyncResp->res.jsonValue["Name"] = "User Account";
2060 asyncResp->res.jsonValue["Description"] = "User Account";
2061 asyncResp->res.jsonValue["Password"] = nullptr;
2062 asyncResp->res.jsonValue["StrictAccountTypes"] = true;
2063
2064 for (const auto& interface : userIt->second)
2065 {
2066 if (interface.first == "xyz.openbmc_project.User.Attributes")
2067 {
2068 const bool* userEnabled = nullptr;
2069 const bool* userLocked = nullptr;
2070 const std::string* userPrivPtr = nullptr;
2071 const bool* userPasswordExpired = nullptr;
2072 const std::vector<std::string>* userGroups = nullptr;
2073
2074 const bool success = sdbusplus::unpackPropertiesNoThrow(
2075 dbus_utils::UnpackErrorPrinter(), interface.second,
2076 "UserEnabled", userEnabled,
2077 "UserLockedForFailedAttempt", userLocked,
2078 "UserPrivilege", userPrivPtr, "UserPasswordExpired",
2079 userPasswordExpired, "UserGroups", userGroups);
2080 if (!success)
2081 {
2082 messages::internalError(asyncResp->res);
2083 return;
2084 }
2085 if (userEnabled == nullptr)
2086 {
2087 BMCWEB_LOG_ERROR("UserEnabled wasn't a bool");
2088 messages::internalError(asyncResp->res);
2089 return;
2090 }
2091 asyncResp->res.jsonValue["Enabled"] = *userEnabled;
2092
2093 if (userLocked == nullptr)
2094 {
2095 BMCWEB_LOG_ERROR("UserLockedForF"
2096 "ailedAttempt "
2097 "wasn't a bool");
2098 messages::internalError(asyncResp->res);
2099 return;
2100 }
2101 asyncResp->res.jsonValue["Locked"] = *userLocked;
2102 nlohmann::json::array_t allowed;
2103 // can only unlock accounts
2104 allowed.emplace_back("false");
2105 asyncResp->res.jsonValue["Locked@Redfish.AllowableValues"] =
2106 std::move(allowed);
2107
2108 if (userPrivPtr == nullptr)
2109 {
2110 BMCWEB_LOG_ERROR("UserPrivilege wasn't a "
2111 "string");
2112 messages::internalError(asyncResp->res);
2113 return;
2114 }
2115 std::string role = getRoleIdFromPrivilege(*userPrivPtr);
2116 if (role.empty())
2117 {
2118 BMCWEB_LOG_ERROR("Invalid user role");
2119 messages::internalError(asyncResp->res);
2120 return;
2121 }
2122 asyncResp->res.jsonValue["RoleId"] = role;
2123
2124 nlohmann::json& roleEntry =
2125 asyncResp->res.jsonValue["Links"]["Role"];
2126 roleEntry["@odata.id"] = boost::urls::format(
2127 "/redfish/v1/AccountService/Roles/{}", role);
2128
2129 if (userPasswordExpired == nullptr)
2130 {
2131 BMCWEB_LOG_ERROR("UserPasswordExpired wasn't a bool");
2132 messages::internalError(asyncResp->res);
2133 return;
2134 }
2135 asyncResp->res.jsonValue["PasswordChangeRequired"] =
2136 *userPasswordExpired;
2137
2138 if (userGroups == nullptr)
2139 {
2140 BMCWEB_LOG_ERROR("userGroups wasn't a string vector");
2141 messages::internalError(asyncResp->res);
2142 return;
2143 }
2144 if (!translateUserGroup(*userGroups, asyncResp->res))
2145 {
2146 BMCWEB_LOG_ERROR("userGroups mapping failed");
2147 messages::internalError(asyncResp->res);
2148 return;
2149 }
2150 }
2151 }
2152
2153 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
2154 "/redfish/v1/AccountService/Accounts/{}", accountName);
2155 asyncResp->res.jsonValue["Id"] = accountName;
2156 asyncResp->res.jsonValue["UserName"] = accountName;
2157 });
2158 }
2159
handleAccountDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & username)2160 inline void handleAccountDelete(
2161 App& app, const crow::Request& req,
2162 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2163 const std::string& username)
2164 {
2165 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2166 {
2167 return;
2168 }
2169
2170 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2171 {
2172 // If authentication is disabled, there are no user accounts
2173 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2174 return;
2175 }
2176 sdbusplus::message::object_path tempObjPath(rootUserDbusPath);
2177 tempObjPath /= username;
2178 const std::string userPath(tempObjPath);
2179
2180 dbus::utility::async_method_call(
2181 asyncResp,
2182 [asyncResp, username](const boost::system::error_code& ec) {
2183 if (ec)
2184 {
2185 messages::resourceNotFound(asyncResp->res, "ManagerAccount",
2186 username);
2187 return;
2188 }
2189
2190 messages::accountRemoved(asyncResp->res);
2191 },
2192 "xyz.openbmc_project.User.Manager", userPath,
2193 "xyz.openbmc_project.Object.Delete", "Delete");
2194 }
2195
handleAccountPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & username)2196 inline void handleAccountPatch(
2197 App& app, const crow::Request& req,
2198 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2199 const std::string& username)
2200 {
2201 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2202 {
2203 return;
2204 }
2205 if constexpr (BMCWEB_INSECURE_DISABLE_AUTH)
2206 {
2207 // If authentication is disabled, there are no user accounts
2208 messages::resourceNotFound(asyncResp->res, "ManagerAccount", username);
2209 return;
2210 }
2211 std::optional<std::string> newUserName;
2212 std::optional<std::string> password;
2213 std::optional<bool> enabled;
2214 std::optional<std::string> roleId;
2215 std::optional<bool> locked;
2216 std::optional<std::vector<std::string>> accountTypes;
2217
2218 if (req.session == nullptr)
2219 {
2220 messages::internalError(asyncResp->res);
2221 return;
2222 }
2223
2224 bool userSelf = (username == req.session->username);
2225
2226 Privileges effectiveUserPrivileges =
2227 redfish::getUserPrivileges(*req.session);
2228 Privileges configureUsers = {"ConfigureUsers"};
2229 bool userHasConfigureUsers =
2230 effectiveUserPrivileges.isSupersetOf(configureUsers) &&
2231 !req.session->isConfigureSelfOnly;
2232 if (userHasConfigureUsers)
2233 {
2234 // Users with ConfigureUsers can modify for all users
2235 if (!json_util::readJsonPatch( //
2236 req, asyncResp->res, //
2237 "AccountTypes", accountTypes, //
2238 "Enabled", enabled, //
2239 "Locked", locked, //
2240 "Password", password, //
2241 "RoleId", roleId, //
2242 "UserName", newUserName //
2243 ))
2244 {
2245 return;
2246 }
2247 }
2248 else
2249 {
2250 // ConfigureSelf accounts can only modify their own account
2251 if (!userSelf)
2252 {
2253 messages::insufficientPrivilege(asyncResp->res);
2254 return;
2255 }
2256
2257 // ConfigureSelf accounts can only modify their password
2258 if (!json_util::readJsonPatch(req, asyncResp->res, "Password",
2259 password))
2260 {
2261 return;
2262 }
2263 }
2264
2265 // if user name is not provided in the patch method or if it
2266 // matches the user name in the URI, then we are treating it as
2267 // updating user properties other then username. If username
2268 // provided doesn't match the URI, then we are treating this as
2269 // user rename request.
2270 if (!newUserName || (newUserName.value() == username))
2271 {
2272 updateUserProperties(asyncResp, username, password, enabled, roleId,
2273 locked, accountTypes, userSelf, req.session);
2274 return;
2275 }
2276 dbus::utility::async_method_call(
2277 asyncResp,
2278 [asyncResp, username, password(std::move(password)),
2279 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)},
2280 locked, userSelf, session = req.session,
2281 accountTypes(std::move(accountTypes))](
2282 const boost::system::error_code& ec, sdbusplus::message_t& m) {
2283 if (ec)
2284 {
2285 userErrorMessageHandler(m.get_error(), asyncResp, newUser,
2286 username);
2287 return;
2288 }
2289
2290 updateUserProperties(asyncResp, newUser, password, enabled, roleId,
2291 locked, accountTypes, userSelf, session);
2292 },
2293 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user",
2294 "xyz.openbmc_project.User.Manager", "RenameUser", username,
2295 *newUserName);
2296 }
2297
requestAccountServiceRoutes(App & app)2298 inline void requestAccountServiceRoutes(App& app)
2299 {
2300 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2301 .privileges(redfish::privileges::headAccountService)
2302 .methods(boost::beast::http::verb::head)(
2303 std::bind_front(handleAccountServiceHead, std::ref(app)));
2304
2305 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2306 .privileges(redfish::privileges::getAccountService)
2307 .methods(boost::beast::http::verb::get)(
2308 std::bind_front(handleAccountServiceGet, std::ref(app)));
2309
2310 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/")
2311 .privileges(redfish::privileges::patchAccountService)
2312 .methods(boost::beast::http::verb::patch)(
2313 std::bind_front(handleAccountServicePatch, std::ref(app)));
2314
2315 BMCWEB_ROUTE(
2316 app,
2317 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/")
2318 .privileges(redfish::privileges::headCertificateCollection)
2319 .methods(boost::beast::http::verb::head)(std::bind_front(
2320 handleAccountServiceClientCertificatesHead, std::ref(app)));
2321
2322 BMCWEB_ROUTE(
2323 app,
2324 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/")
2325 .privileges(redfish::privileges::getCertificateCollection)
2326 .methods(boost::beast::http::verb::get)(std::bind_front(
2327 handleAccountServiceClientCertificatesGet, std::ref(app)));
2328
2329 BMCWEB_ROUTE(
2330 app,
2331 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/")
2332 .privileges(redfish::privileges::headCertificate)
2333 .methods(boost::beast::http::verb::head)(std::bind_front(
2334 handleAccountServiceClientCertificatesInstanceHead, std::ref(app)));
2335
2336 BMCWEB_ROUTE(
2337 app,
2338 "/redfish/v1/AccountService/MultiFactorAuth/ClientCertificate/Certificates/<str>/")
2339 .privileges(redfish::privileges::getCertificate)
2340 .methods(boost::beast::http::verb::get)(std::bind_front(
2341 handleAccountServiceClientCertificatesInstanceGet, std::ref(app)));
2342
2343 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2344 .privileges(redfish::privileges::headManagerAccountCollection)
2345 .methods(boost::beast::http::verb::head)(
2346 std::bind_front(handleAccountCollectionHead, std::ref(app)));
2347
2348 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2349 .privileges(redfish::privileges::getManagerAccountCollection)
2350 .methods(boost::beast::http::verb::get)(
2351 std::bind_front(handleAccountCollectionGet, std::ref(app)));
2352
2353 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/")
2354 .privileges(redfish::privileges::postManagerAccountCollection)
2355 .methods(boost::beast::http::verb::post)(
2356 std::bind_front(handleAccountCollectionPost, std::ref(app)));
2357
2358 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2359 .privileges(redfish::privileges::headManagerAccount)
2360 .methods(boost::beast::http::verb::head)(
2361 std::bind_front(handleAccountHead, std::ref(app)));
2362
2363 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2364 .privileges(redfish::privileges::getManagerAccount)
2365 .methods(boost::beast::http::verb::get)(
2366 std::bind_front(handleAccountGet, std::ref(app)));
2367
2368 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2369 // TODO this privilege should be using the generated endpoints, but
2370 // because of the special handling of ConfigureSelf, it's not able to
2371 // yet
2372 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}})
2373 .methods(boost::beast::http::verb::patch)(
2374 std::bind_front(handleAccountPatch, std::ref(app)));
2375
2376 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/")
2377 .privileges(redfish::privileges::deleteManagerAccount)
2378 .methods(boost::beast::http::verb::delete_)(
2379 std::bind_front(handleAccountDelete, std::ref(app)));
2380 }
2381
2382 } // namespace redfish
2383