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