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