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