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