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