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