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