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