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