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