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