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