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