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