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 #include "node.hpp" 18 19 #include <dbus_utility.hpp> 20 #include <error_messages.hpp> 21 #include <openbmc_dbus_rest.hpp> 22 #include <utils/json_utils.hpp> 23 #include <variant> 24 25 namespace redfish 26 { 27 28 constexpr const char* ldapConfigObject = 29 "/xyz/openbmc_project/user/ldap/openldap"; 30 constexpr const char* ADConfigObject = 31 "/xyz/openbmc_project/user/ldap/active_directory"; 32 33 constexpr const char* ldapRootObject = "/xyz/openbmc_project/user/ldap"; 34 constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config"; 35 constexpr const char* ldapConfigInterface = 36 "xyz.openbmc_project.User.Ldap.Config"; 37 constexpr const char* ldapCreateInterface = 38 "xyz.openbmc_project.User.Ldap.Create"; 39 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable"; 40 constexpr const char* ldapPrivMapperInterface = 41 "xyz.openbmc_project.User.PrivilegeMapper"; 42 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; 43 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties"; 44 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper"; 45 constexpr const char* mapperObjectPath = "/xyz/openbmc_project/object_mapper"; 46 constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper"; 47 48 struct LDAPRoleMapData 49 { 50 std::string groupName; 51 std::string privilege; 52 }; 53 54 struct LDAPConfigData 55 { 56 std::string uri{}; 57 std::string bindDN{}; 58 std::string baseDN{}; 59 std::string searchScope{}; 60 std::string serverType{}; 61 bool serviceEnabled = false; 62 std::string userNameAttribute{}; 63 std::string groupAttribute{}; 64 std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList; 65 }; 66 67 using ManagedObjectType = std::vector<std::pair< 68 sdbusplus::message::object_path, 69 boost::container::flat_map< 70 std::string, boost::container::flat_map< 71 std::string, std::variant<bool, std::string>>>>>; 72 using GetObjectType = 73 std::vector<std::pair<std::string, std::vector<std::string>>>; 74 75 inline std::string getRoleIdFromPrivilege(std::string_view role) 76 { 77 if (role == "priv-admin") 78 { 79 return "Administrator"; 80 } 81 else if (role == "priv-callback") 82 { 83 return "Callback"; 84 } 85 else if (role == "priv-user") 86 { 87 return "ReadOnly"; 88 } 89 else if (role == "priv-operator") 90 { 91 return "Operator"; 92 } 93 return ""; 94 } 95 inline std::string getPrivilegeFromRoleId(std::string_view role) 96 { 97 if (role == "Administrator") 98 { 99 return "priv-admin"; 100 } 101 else if (role == "Callback") 102 { 103 return "priv-callback"; 104 } 105 else if (role == "ReadOnly") 106 { 107 return "priv-user"; 108 } 109 else if (role == "Operator") 110 { 111 return "priv-operator"; 112 } 113 return ""; 114 } 115 116 void userErrorMessageHandler(const sd_bus_error* e, 117 std::shared_ptr<AsyncResp> asyncResp, 118 const std::string& newUser, 119 const std::string& username) 120 { 121 const char* errorMessage = e->name; 122 if (e == nullptr) 123 { 124 messages::internalError(asyncResp->res); 125 return; 126 } 127 128 if (strcmp(errorMessage, 129 "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0) 130 { 131 messages::resourceAlreadyExists(asyncResp->res, 132 "#ManagerAccount.v1_0_3.ManagerAccount", 133 "UserName", newUser); 134 } 135 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 136 "UserNameDoesNotExist") == 0) 137 { 138 messages::resourceNotFound( 139 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", username); 140 } 141 else if (strcmp(errorMessage, 142 "xyz.openbmc_project.Common.Error.InvalidArgument") == 0) 143 { 144 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 145 } 146 else if (strcmp(errorMessage, 147 "xyz.openbmc_project.User.Common.Error.NoResource") == 0) 148 { 149 messages::createLimitReachedForResource(asyncResp->res); 150 } 151 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 152 "UserNameGroupFail") == 0) 153 { 154 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 155 } 156 else 157 { 158 messages::internalError(asyncResp->res); 159 } 160 161 return; 162 } 163 164 void parseLDAPConfigData(nlohmann::json& json_response, 165 const LDAPConfigData& confData, 166 const std::string& ldapType) 167 { 168 std::string service = 169 (ldapType == "LDAP") ? "LDAPService" : "ActiveDirectoryService"; 170 nlohmann::json ldap = { 171 {"AccountProviderType", service}, 172 {"ServiceEnabled", confData.serviceEnabled}, 173 {"ServiceAddresses", nlohmann::json::array({confData.uri})}, 174 {"Authentication", 175 {{"AuthenticationType", "UsernameAndPassword"}, 176 {"Username", confData.bindDN}, 177 {"Password", nullptr}}}, 178 {"LDAPService", 179 {{"SearchSettings", 180 {{"BaseDistinguishedNames", 181 nlohmann::json::array({confData.baseDN})}, 182 {"UsernameAttribute", confData.userNameAttribute}, 183 {"GroupsAttribute", confData.groupAttribute}}}}}, 184 }; 185 186 json_response[ldapType].update(std::move(ldap)); 187 188 nlohmann::json& roleMapArray = json_response[ldapType]["RemoteRoleMapping"]; 189 roleMapArray = nlohmann::json::array(); 190 for (auto& obj : confData.groupRoleList) 191 { 192 BMCWEB_LOG_DEBUG << "Pushing the data groupName=" 193 << obj.second.groupName << "\n"; 194 roleMapArray.push_back( 195 {nlohmann::json::array({"RemoteGroup", obj.second.groupName}), 196 nlohmann::json::array( 197 {"LocalRole", getRoleIdFromPrivilege(obj.second.privilege)})}); 198 } 199 } 200 201 /** 202 * @brief validates given JSON input and then calls appropriate method to 203 * create, to delete or to set Rolemapping object based on the given input. 204 * 205 */ 206 static void handleRoleMapPatch( 207 const std::shared_ptr<AsyncResp>& asyncResp, 208 const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData, 209 const std::string& serverType, std::vector<nlohmann::json>& input) 210 { 211 for (size_t index = 0; index < input.size(); index++) 212 { 213 nlohmann::json& thisJson = input[index]; 214 215 if (thisJson.is_null()) 216 { 217 // delete the existing object 218 if (index < roleMapObjData.size()) 219 { 220 crow::connections::systemBus->async_method_call( 221 [asyncResp, roleMapObjData, serverType, 222 index](const boost::system::error_code ec) { 223 if (ec) 224 { 225 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 226 messages::internalError(asyncResp->res); 227 return; 228 } 229 asyncResp->res 230 .jsonValue[serverType]["RemoteRoleMapping"][index] = 231 nullptr; 232 }, 233 ldapDbusService, roleMapObjData[index].first, 234 "xyz.openbmc_project.Object.Delete", "Delete"); 235 } 236 else 237 { 238 BMCWEB_LOG_ERROR << "Can't delete the object"; 239 messages::propertyValueTypeError( 240 asyncResp->res, thisJson.dump(), 241 "RemoteRoleMapping/" + std::to_string(index)); 242 return; 243 } 244 } 245 else if (thisJson.empty()) 246 { 247 // Don't do anything for the empty objects,parse next json 248 // eg {"RemoteRoleMapping",[{}]} 249 } 250 else 251 { 252 // update/create the object 253 std::optional<std::string> remoteGroup; 254 std::optional<std::string> localRole; 255 256 if (!json_util::readJson(thisJson, asyncResp->res, "RemoteGroup", 257 remoteGroup, "LocalRole", localRole)) 258 { 259 continue; 260 } 261 262 // Update existing RoleMapping Object 263 if (index < roleMapObjData.size()) 264 { 265 BMCWEB_LOG_DEBUG << "Update Role Map Object"; 266 // If "RemoteGroup" info is provided 267 if (remoteGroup) 268 { 269 crow::connections::systemBus->async_method_call( 270 [asyncResp, roleMapObjData, serverType, index, 271 remoteGroup](const boost::system::error_code ec) { 272 if (ec) 273 { 274 BMCWEB_LOG_ERROR << "DBUS response error: " 275 << ec; 276 messages::internalError(asyncResp->res); 277 return; 278 } 279 asyncResp->res 280 .jsonValue[serverType]["RemoteRoleMapping"] 281 [index]["RemoteGroup"] = *remoteGroup; 282 }, 283 ldapDbusService, roleMapObjData[index].first, 284 propertyInterface, "Set", 285 "xyz.openbmc_project.User.PrivilegeMapperEntry", 286 "GroupName", 287 std::variant<std::string>(std::move(*remoteGroup))); 288 } 289 290 // If "LocalRole" info is provided 291 if (localRole) 292 { 293 crow::connections::systemBus->async_method_call( 294 [asyncResp, roleMapObjData, serverType, index, 295 localRole](const boost::system::error_code ec) { 296 if (ec) 297 { 298 BMCWEB_LOG_ERROR << "DBUS response error: " 299 << ec; 300 messages::internalError(asyncResp->res); 301 return; 302 } 303 asyncResp->res 304 .jsonValue[serverType]["RemoteRoleMapping"] 305 [index]["LocalRole"] = *localRole; 306 }, 307 ldapDbusService, roleMapObjData[index].first, 308 propertyInterface, "Set", 309 "xyz.openbmc_project.User.PrivilegeMapperEntry", 310 "Privilege", 311 std::variant<std::string>( 312 getPrivilegeFromRoleId(std::move(*localRole)))); 313 } 314 } 315 // Create a new RoleMapping Object. 316 else 317 { 318 BMCWEB_LOG_DEBUG 319 << "setRoleMappingProperties: Creating new Object"; 320 std::string pathString = 321 "RemoteRoleMapping/" + std::to_string(index); 322 323 if (!localRole) 324 { 325 messages::propertyMissing(asyncResp->res, 326 pathString + "/LocalRole"); 327 continue; 328 } 329 if (!remoteGroup) 330 { 331 messages::propertyMissing(asyncResp->res, 332 pathString + "/RemoteGroup"); 333 continue; 334 } 335 336 std::string dbusObjectPath; 337 if (serverType == "ActiveDirectory") 338 { 339 dbusObjectPath = ADConfigObject; 340 } 341 else if (serverType == "LDAP") 342 { 343 dbusObjectPath = ldapConfigObject; 344 } 345 346 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup 347 << ",LocalRole=" << *localRole; 348 349 crow::connections::systemBus->async_method_call( 350 [asyncResp, serverType, localRole, 351 remoteGroup](const boost::system::error_code ec) { 352 if (ec) 353 { 354 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 355 messages::internalError(asyncResp->res); 356 return; 357 } 358 nlohmann::json& remoteRoleJson = 359 asyncResp->res 360 .jsonValue[serverType]["RemoteRoleMapping"]; 361 remoteRoleJson.push_back( 362 {{"LocalRole", *localRole}, 363 {"RemoteGroup", *remoteGroup}}); 364 }, 365 ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, 366 "Create", std::move(*remoteGroup), 367 getPrivilegeFromRoleId(std::move(*localRole))); 368 } 369 } 370 } 371 } 372 373 /** 374 * Function that retrieves all properties for LDAP config object 375 * into JSON 376 */ 377 template <typename CallbackFunc> 378 inline void getLDAPConfigData(const std::string& ldapType, 379 CallbackFunc&& callback) 380 { 381 382 const std::array<const char*, 2> interfaces = {ldapEnableInterface, 383 ldapConfigInterface}; 384 385 crow::connections::systemBus->async_method_call( 386 [callback, ldapType](const boost::system::error_code ec, 387 const GetObjectType& resp) { 388 LDAPConfigData confData{}; 389 if (ec || resp.empty()) 390 { 391 BMCWEB_LOG_ERROR << "DBUS response error during getting of " 392 "service name: " 393 << ec; 394 callback(false, confData, ldapType); 395 return; 396 } 397 std::string service = resp.begin()->first; 398 crow::connections::systemBus->async_method_call( 399 [callback, ldapType](const boost::system::error_code error_code, 400 const ManagedObjectType& ldapObjects) { 401 LDAPConfigData confData{}; 402 if (error_code) 403 { 404 callback(false, confData, ldapType); 405 BMCWEB_LOG_ERROR << "D-Bus responses error: " 406 << error_code; 407 return; 408 } 409 410 std::string ldapDbusType; 411 std::string searchString; 412 413 if (ldapType == "LDAP") 414 { 415 ldapDbusType = "xyz.openbmc_project.User.Ldap.Config." 416 "Type.OpenLdap"; 417 searchString = "openldap"; 418 } 419 else if (ldapType == "ActiveDirectory") 420 { 421 ldapDbusType = 422 "xyz.openbmc_project.User.Ldap.Config.Type." 423 "ActiveDirectory"; 424 searchString = "active_directory"; 425 } 426 else 427 { 428 BMCWEB_LOG_ERROR 429 << "Can't get the DbusType for the given type=" 430 << ldapType; 431 callback(false, confData, ldapType); 432 return; 433 } 434 435 std::string ldapEnableInterfaceStr = ldapEnableInterface; 436 std::string ldapConfigInterfaceStr = ldapConfigInterface; 437 438 for (const auto& object : ldapObjects) 439 { 440 // let's find the object whose ldap type is equal to the 441 // given type 442 if (object.first.str.find(searchString) == 443 std::string::npos) 444 { 445 continue; 446 } 447 448 for (const auto& interface : object.second) 449 { 450 if (interface.first == ldapEnableInterfaceStr) 451 { 452 // rest of the properties are string. 453 for (const auto& property : interface.second) 454 { 455 if (property.first == "Enabled") 456 { 457 const bool* value = 458 std::get_if<bool>(&property.second); 459 if (value == nullptr) 460 { 461 continue; 462 } 463 confData.serviceEnabled = *value; 464 break; 465 } 466 } 467 } 468 else if (interface.first == ldapConfigInterfaceStr) 469 { 470 471 for (const auto& property : interface.second) 472 { 473 const std::string* strValue = 474 std::get_if<std::string>( 475 &property.second); 476 if (strValue == nullptr) 477 { 478 continue; 479 } 480 if (property.first == "LDAPServerURI") 481 { 482 confData.uri = *strValue; 483 } 484 else if (property.first == "LDAPBindDN") 485 { 486 confData.bindDN = *strValue; 487 } 488 else if (property.first == "LDAPBaseDN") 489 { 490 confData.baseDN = *strValue; 491 } 492 else if (property.first == 493 "LDAPSearchScope") 494 { 495 confData.searchScope = *strValue; 496 } 497 else if (property.first == 498 "GroupNameAttribute") 499 { 500 confData.groupAttribute = *strValue; 501 } 502 else if (property.first == 503 "UserNameAttribute") 504 { 505 confData.userNameAttribute = *strValue; 506 } 507 else if (property.first == "LDAPType") 508 { 509 confData.serverType = *strValue; 510 } 511 } 512 } 513 else if (interface.first == 514 "xyz.openbmc_project.User." 515 "PrivilegeMapperEntry") 516 { 517 LDAPRoleMapData roleMapData{}; 518 for (const auto& property : interface.second) 519 { 520 const std::string* strValue = 521 std::get_if<std::string>( 522 &property.second); 523 524 if (strValue == nullptr) 525 { 526 continue; 527 } 528 529 if (property.first == "GroupName") 530 { 531 roleMapData.groupName = *strValue; 532 } 533 else if (property.first == "Privilege") 534 { 535 roleMapData.privilege = *strValue; 536 } 537 } 538 539 confData.groupRoleList.emplace_back( 540 object.first.str, roleMapData); 541 } 542 } 543 } 544 callback(true, confData, ldapType); 545 }, 546 service, ldapRootObject, dbusObjManagerIntf, 547 "GetManagedObjects"); 548 }, 549 mapperBusName, mapperObjectPath, mapperIntf, "GetObject", 550 ldapConfigObject, interfaces); 551 } 552 553 class AccountService : public Node 554 { 555 public: 556 AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/") 557 { 558 entityPrivileges = { 559 {boost::beast::http::verb::get, 560 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 561 {boost::beast::http::verb::head, {{"Login"}}}, 562 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 563 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 564 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 565 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 566 } 567 568 private: 569 /** 570 * @brief parses the authentication section under the LDAP 571 * @param input JSON data 572 * @param asyncResp pointer to the JSON response 573 * @param userName userName to be filled from the given JSON. 574 * @param password password to be filled from the given JSON. 575 */ 576 void 577 parseLDAPAuthenticationJson(nlohmann::json input, 578 const std::shared_ptr<AsyncResp>& asyncResp, 579 std::optional<std::string>& username, 580 std::optional<std::string>& password) 581 { 582 std::optional<std::string> authType; 583 584 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 585 authType, "Username", username, "Password", 586 password)) 587 { 588 return; 589 } 590 if (!authType) 591 { 592 return; 593 } 594 if (*authType != "UsernameAndPassword") 595 { 596 messages::propertyValueNotInList(asyncResp->res, *authType, 597 "AuthenticationType"); 598 return; 599 } 600 } 601 /** 602 * @brief parses the LDAPService section under the LDAP 603 * @param input JSON data 604 * @param asyncResp pointer to the JSON response 605 * @param baseDNList baseDN to be filled from the given JSON. 606 * @param userNameAttribute userName to be filled from the given JSON. 607 * @param groupaAttribute password to be filled from the given JSON. 608 */ 609 610 void parseLDAPServiceJson( 611 nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp, 612 std::optional<std::vector<std::string>>& baseDNList, 613 std::optional<std::string>& userNameAttribute, 614 std::optional<std::string>& groupsAttribute) 615 { 616 std::optional<nlohmann::json> searchSettings; 617 618 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 619 searchSettings)) 620 { 621 return; 622 } 623 if (!searchSettings) 624 { 625 return; 626 } 627 if (!json_util::readJson(*searchSettings, asyncResp->res, 628 "BaseDistinguishedNames", baseDNList, 629 "UsernameAttribute", userNameAttribute, 630 "GroupsAttribute", groupsAttribute)) 631 { 632 return; 633 } 634 } 635 /** 636 * @brief updates the LDAP server address and updates the 637 json response with the new value. 638 * @param serviceAddressList address to be updated. 639 * @param asyncResp pointer to the JSON response 640 * @param ldapServerElementName Type of LDAP 641 server(openLDAP/ActiveDirectory) 642 */ 643 644 void handleServiceAddressPatch( 645 const std::vector<std::string>& serviceAddressList, 646 const std::shared_ptr<AsyncResp>& asyncResp, 647 const std::string& ldapServerElementName, 648 const std::string& ldapConfigObject) 649 { 650 crow::connections::systemBus->async_method_call( 651 [asyncResp, ldapServerElementName, 652 serviceAddressList](const boost::system::error_code ec) { 653 if (ec) 654 { 655 BMCWEB_LOG_DEBUG 656 << "Error Occured in updating the service address"; 657 messages::internalError(asyncResp->res); 658 return; 659 } 660 std::vector<std::string> modifiedserviceAddressList = { 661 serviceAddressList.front()}; 662 asyncResp->res 663 .jsonValue[ldapServerElementName]["ServiceAddresses"] = 664 modifiedserviceAddressList; 665 if ((serviceAddressList).size() > 1) 666 { 667 messages::propertyValueModified(asyncResp->res, 668 "ServiceAddresses", 669 serviceAddressList.front()); 670 } 671 BMCWEB_LOG_DEBUG << "Updated the service address"; 672 }, 673 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 674 ldapConfigInterface, "LDAPServerURI", 675 std::variant<std::string>(serviceAddressList.front())); 676 } 677 /** 678 * @brief updates the LDAP Bind DN and updates the 679 json response with the new value. 680 * @param username name of the user which needs to be updated. 681 * @param asyncResp pointer to the JSON response 682 * @param ldapServerElementName Type of LDAP 683 server(openLDAP/ActiveDirectory) 684 */ 685 686 void handleUserNamePatch(const std::string& username, 687 const std::shared_ptr<AsyncResp>& asyncResp, 688 const std::string& ldapServerElementName, 689 const std::string& ldapConfigObject) 690 { 691 crow::connections::systemBus->async_method_call( 692 [asyncResp, username, 693 ldapServerElementName](const boost::system::error_code ec) { 694 if (ec) 695 { 696 BMCWEB_LOG_DEBUG 697 << "Error occured in updating the username"; 698 messages::internalError(asyncResp->res); 699 return; 700 } 701 asyncResp->res.jsonValue[ldapServerElementName] 702 ["Authentication"]["Username"] = 703 username; 704 BMCWEB_LOG_DEBUG << "Updated the username"; 705 }, 706 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 707 ldapConfigInterface, "LDAPBindDN", 708 std::variant<std::string>(username)); 709 } 710 711 /** 712 * @brief updates the LDAP password 713 * @param password : ldap password which needs to be updated. 714 * @param asyncResp pointer to the JSON response 715 * @param ldapServerElementName Type of LDAP 716 * server(openLDAP/ActiveDirectory) 717 */ 718 719 void handlePasswordPatch(const std::string& password, 720 const std::shared_ptr<AsyncResp>& asyncResp, 721 const std::string& ldapServerElementName, 722 const std::string& ldapConfigObject) 723 { 724 crow::connections::systemBus->async_method_call( 725 [asyncResp, password, 726 ldapServerElementName](const boost::system::error_code ec) { 727 if (ec) 728 { 729 BMCWEB_LOG_DEBUG 730 << "Error occured in updating the password"; 731 messages::internalError(asyncResp->res); 732 return; 733 } 734 asyncResp->res.jsonValue[ldapServerElementName] 735 ["Authentication"]["Password"] = ""; 736 BMCWEB_LOG_DEBUG << "Updated the password"; 737 }, 738 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 739 ldapConfigInterface, "LDAPBindDNPassword", 740 std::variant<std::string>(password)); 741 } 742 743 /** 744 * @brief updates the LDAP BaseDN and updates the 745 json response with the new value. 746 * @param baseDNList baseDN list which needs to be updated. 747 * @param asyncResp pointer to the JSON response 748 * @param ldapServerElementName Type of LDAP 749 server(openLDAP/ActiveDirectory) 750 */ 751 752 void handleBaseDNPatch(const std::vector<std::string>& baseDNList, 753 const std::shared_ptr<AsyncResp>& asyncResp, 754 const std::string& ldapServerElementName, 755 const std::string& ldapConfigObject) 756 { 757 crow::connections::systemBus->async_method_call( 758 [asyncResp, baseDNList, 759 ldapServerElementName](const boost::system::error_code ec) { 760 if (ec) 761 { 762 BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN"; 763 messages::internalError(asyncResp->res); 764 return; 765 } 766 auto& serverTypeJson = 767 asyncResp->res.jsonValue[ldapServerElementName]; 768 auto& searchSettingsJson = 769 serverTypeJson["LDAPService"]["SearchSettings"]; 770 std::vector<std::string> modifiedBaseDNList = { 771 baseDNList.front()}; 772 searchSettingsJson["BaseDistinguishedNames"] = 773 modifiedBaseDNList; 774 if (baseDNList.size() > 1) 775 { 776 messages::propertyValueModified(asyncResp->res, 777 "BaseDistinguishedNames", 778 baseDNList.front()); 779 } 780 BMCWEB_LOG_DEBUG << "Updated the base DN"; 781 }, 782 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 783 ldapConfigInterface, "LDAPBaseDN", 784 std::variant<std::string>(baseDNList.front())); 785 } 786 /** 787 * @brief updates the LDAP user name attribute and updates the 788 json response with the new value. 789 * @param userNameAttribute attribute to be updated. 790 * @param asyncResp pointer to the JSON response 791 * @param ldapServerElementName Type of LDAP 792 server(openLDAP/ActiveDirectory) 793 */ 794 795 void handleUserNameAttrPatch(const std::string& userNameAttribute, 796 const std::shared_ptr<AsyncResp>& asyncResp, 797 const std::string& ldapServerElementName, 798 const std::string& ldapConfigObject) 799 { 800 crow::connections::systemBus->async_method_call( 801 [asyncResp, userNameAttribute, 802 ldapServerElementName](const boost::system::error_code ec) { 803 if (ec) 804 { 805 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 806 "username attribute"; 807 messages::internalError(asyncResp->res); 808 return; 809 } 810 auto& serverTypeJson = 811 asyncResp->res.jsonValue[ldapServerElementName]; 812 auto& searchSettingsJson = 813 serverTypeJson["LDAPService"]["SearchSettings"]; 814 searchSettingsJson["UsernameAttribute"] = userNameAttribute; 815 BMCWEB_LOG_DEBUG << "Updated the user name attr."; 816 }, 817 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 818 ldapConfigInterface, "UserNameAttribute", 819 std::variant<std::string>(userNameAttribute)); 820 } 821 /** 822 * @brief updates the LDAP group attribute and updates the 823 json response with the new value. 824 * @param groupsAttribute attribute to be updated. 825 * @param asyncResp pointer to the JSON response 826 * @param ldapServerElementName Type of LDAP 827 server(openLDAP/ActiveDirectory) 828 */ 829 830 void handleGroupNameAttrPatch(const std::string& groupsAttribute, 831 const std::shared_ptr<AsyncResp>& asyncResp, 832 const std::string& ldapServerElementName, 833 const std::string& ldapConfigObject) 834 { 835 crow::connections::systemBus->async_method_call( 836 [asyncResp, groupsAttribute, 837 ldapServerElementName](const boost::system::error_code ec) { 838 if (ec) 839 { 840 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 841 "groupname attribute"; 842 messages::internalError(asyncResp->res); 843 return; 844 } 845 auto& serverTypeJson = 846 asyncResp->res.jsonValue[ldapServerElementName]; 847 auto& searchSettingsJson = 848 serverTypeJson["LDAPService"]["SearchSettings"]; 849 searchSettingsJson["GroupsAttribute"] = groupsAttribute; 850 BMCWEB_LOG_DEBUG << "Updated the groupname attr"; 851 }, 852 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 853 ldapConfigInterface, "GroupNameAttribute", 854 std::variant<std::string>(groupsAttribute)); 855 } 856 /** 857 * @brief updates the LDAP service enable and updates the 858 json response with the new value. 859 * @param input JSON data. 860 * @param asyncResp pointer to the JSON response 861 * @param ldapServerElementName Type of LDAP 862 server(openLDAP/ActiveDirectory) 863 */ 864 865 void handleServiceEnablePatch(bool serviceEnabled, 866 const std::shared_ptr<AsyncResp>& asyncResp, 867 const std::string& ldapServerElementName, 868 const std::string& ldapConfigObject) 869 { 870 crow::connections::systemBus->async_method_call( 871 [asyncResp, serviceEnabled, 872 ldapServerElementName](const boost::system::error_code ec) { 873 if (ec) 874 { 875 BMCWEB_LOG_DEBUG 876 << "Error Occured in Updating the service enable"; 877 messages::internalError(asyncResp->res); 878 return; 879 } 880 asyncResp->res 881 .jsonValue[ldapServerElementName]["ServiceEnabled"] = 882 serviceEnabled; 883 BMCWEB_LOG_DEBUG << "Updated Service enable = " 884 << serviceEnabled; 885 }, 886 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 887 ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled)); 888 } 889 890 /** 891 * @brief Get the required values from the given JSON, validates the 892 * value and create the LDAP config object. 893 * @param input JSON data 894 * @param asyncResp pointer to the JSON response 895 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 896 */ 897 898 void handleLDAPPatch(nlohmann::json& input, 899 const std::shared_ptr<AsyncResp>& asyncResp, 900 const crow::Request& req, 901 const std::vector<std::string>& params, 902 const std::string& serverType) 903 { 904 std::string dbusObjectPath; 905 if (serverType == "ActiveDirectory") 906 { 907 dbusObjectPath = ADConfigObject; 908 } 909 else if (serverType == "LDAP") 910 { 911 dbusObjectPath = ldapConfigObject; 912 } 913 914 std::optional<nlohmann::json> authentication; 915 std::optional<nlohmann::json> ldapService; 916 std::optional<std::string> accountProviderType; 917 std::optional<std::vector<std::string>> serviceAddressList; 918 std::optional<bool> serviceEnabled; 919 std::optional<std::vector<std::string>> baseDNList; 920 std::optional<std::string> userNameAttribute; 921 std::optional<std::string> groupsAttribute; 922 std::optional<std::string> userName; 923 std::optional<std::string> password; 924 std::optional<std::vector<nlohmann::json>> remoteRoleMapData; 925 926 if (!json_util::readJson(input, asyncResp->res, "Authentication", 927 authentication, "LDAPService", ldapService, 928 "ServiceAddresses", serviceAddressList, 929 "AccountProviderType", accountProviderType, 930 "ServiceEnabled", serviceEnabled, 931 "RemoteRoleMapping", remoteRoleMapData)) 932 { 933 return; 934 } 935 936 if (authentication) 937 { 938 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 939 password); 940 } 941 if (ldapService) 942 { 943 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 944 userNameAttribute, groupsAttribute); 945 } 946 if (accountProviderType) 947 { 948 messages::propertyNotWritable(asyncResp->res, 949 "AccountProviderType"); 950 } 951 if (serviceAddressList) 952 { 953 if ((*serviceAddressList).size() == 0) 954 { 955 messages::propertyValueNotInList(asyncResp->res, "[]", 956 "ServiceAddress"); 957 return; 958 } 959 } 960 if (baseDNList) 961 { 962 if ((*baseDNList).size() == 0) 963 { 964 messages::propertyValueNotInList(asyncResp->res, "[]", 965 "BaseDistinguishedNames"); 966 return; 967 } 968 } 969 970 // nothing to update, then return 971 if (!userName && !password && !serviceAddressList && !baseDNList && 972 !userNameAttribute && !groupsAttribute && !serviceEnabled && 973 !remoteRoleMapData) 974 { 975 return; 976 } 977 978 // Get the existing resource first then keep modifying 979 // whenever any property gets updated. 980 getLDAPConfigData(serverType, [this, asyncResp, userName, password, 981 baseDNList, userNameAttribute, 982 groupsAttribute, accountProviderType, 983 serviceAddressList, serviceEnabled, 984 dbusObjectPath, remoteRoleMapData]( 985 bool success, LDAPConfigData confData, 986 const std::string& serverType) { 987 if (!success) 988 { 989 messages::internalError(asyncResp->res); 990 return; 991 } 992 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType); 993 if (confData.serviceEnabled) 994 { 995 // Disable the service first and update the rest of 996 // the properties. 997 handleServiceEnablePatch(false, asyncResp, serverType, 998 dbusObjectPath); 999 } 1000 1001 if (serviceAddressList) 1002 { 1003 handleServiceAddressPatch(*serviceAddressList, asyncResp, 1004 serverType, dbusObjectPath); 1005 } 1006 if (userName) 1007 { 1008 handleUserNamePatch(*userName, asyncResp, serverType, 1009 dbusObjectPath); 1010 } 1011 if (password) 1012 { 1013 handlePasswordPatch(*password, asyncResp, serverType, 1014 dbusObjectPath); 1015 } 1016 1017 if (baseDNList) 1018 { 1019 handleBaseDNPatch(*baseDNList, asyncResp, serverType, 1020 dbusObjectPath); 1021 } 1022 if (userNameAttribute) 1023 { 1024 handleUserNameAttrPatch(*userNameAttribute, asyncResp, 1025 serverType, dbusObjectPath); 1026 } 1027 if (groupsAttribute) 1028 { 1029 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, 1030 serverType, dbusObjectPath); 1031 } 1032 if (serviceEnabled) 1033 { 1034 // if user has given the value as true then enable 1035 // the service. if user has given false then no-op 1036 // as service is already stopped. 1037 if (*serviceEnabled) 1038 { 1039 handleServiceEnablePatch(*serviceEnabled, asyncResp, 1040 serverType, dbusObjectPath); 1041 } 1042 } 1043 else 1044 { 1045 // if user has not given the service enabled value 1046 // then revert it to the same state as it was 1047 // before. 1048 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1049 serverType, dbusObjectPath); 1050 } 1051 1052 if (remoteRoleMapData) 1053 { 1054 std::vector<nlohmann::json> remoteRoleMap = 1055 std::move(*remoteRoleMapData); 1056 1057 handleRoleMapPatch(asyncResp, confData.groupRoleList, 1058 serverType, remoteRoleMap); 1059 } 1060 }); 1061 } 1062 1063 void doGet(crow::Response& res, const crow::Request& req, 1064 const std::vector<std::string>& params) override 1065 { 1066 auto asyncResp = std::make_shared<AsyncResp>(res); 1067 res.jsonValue = { 1068 {"@odata.context", "/redfish/v1/" 1069 "$metadata#AccountService.AccountService"}, 1070 {"@odata.id", "/redfish/v1/AccountService"}, 1071 {"@odata.type", "#AccountService." 1072 "v1_4_0.AccountService"}, 1073 {"Id", "AccountService"}, 1074 {"Name", "Account Service"}, 1075 {"Description", "Account Service"}, 1076 {"ServiceEnabled", true}, 1077 {"MaxPasswordLength", 20}, 1078 {"Accounts", 1079 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 1080 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}, 1081 {"LDAP", 1082 {{"Certificates", 1083 {{"@odata.id", 1084 "/redfish/v1/AccountService/LDAP/Certificates"}}}}}}; 1085 crow::connections::systemBus->async_method_call( 1086 [asyncResp]( 1087 const boost::system::error_code ec, 1088 const std::vector<std::pair< 1089 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 1090 propertiesList) { 1091 if (ec) 1092 { 1093 messages::internalError(asyncResp->res); 1094 return; 1095 } 1096 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1097 << "properties for AccountService"; 1098 for (const std::pair<std::string, 1099 std::variant<uint32_t, uint16_t, uint8_t>>& 1100 property : propertiesList) 1101 { 1102 if (property.first == "MinPasswordLength") 1103 { 1104 const uint8_t* value = 1105 std::get_if<uint8_t>(&property.second); 1106 if (value != nullptr) 1107 { 1108 asyncResp->res.jsonValue["MinPasswordLength"] = 1109 *value; 1110 } 1111 } 1112 if (property.first == "AccountUnlockTimeout") 1113 { 1114 const uint32_t* value = 1115 std::get_if<uint32_t>(&property.second); 1116 if (value != nullptr) 1117 { 1118 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1119 *value; 1120 } 1121 } 1122 if (property.first == "MaxLoginAttemptBeforeLockout") 1123 { 1124 const uint16_t* value = 1125 std::get_if<uint16_t>(&property.second); 1126 if (value != nullptr) 1127 { 1128 asyncResp->res 1129 .jsonValue["AccountLockoutThreshold"] = *value; 1130 } 1131 } 1132 } 1133 }, 1134 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1135 "org.freedesktop.DBus.Properties", "GetAll", 1136 "xyz.openbmc_project.User.AccountPolicy"); 1137 1138 auto callback = [asyncResp](bool success, LDAPConfigData& confData, 1139 const std::string& ldapType) { 1140 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1141 }; 1142 1143 getLDAPConfigData("LDAP", callback); 1144 getLDAPConfigData("ActiveDirectory", callback); 1145 } 1146 1147 void doPatch(crow::Response& res, const crow::Request& req, 1148 const std::vector<std::string>& params) override 1149 { 1150 auto asyncResp = std::make_shared<AsyncResp>(res); 1151 1152 std::optional<uint32_t> unlockTimeout; 1153 std::optional<uint16_t> lockoutThreshold; 1154 std::optional<uint16_t> minPasswordLength; 1155 std::optional<uint16_t> maxPasswordLength; 1156 std::optional<nlohmann::json> ldapObject; 1157 std::optional<nlohmann::json> activeDirectoryObject; 1158 1159 if (!json_util::readJson(req, res, "AccountLockoutDuration", 1160 unlockTimeout, "AccountLockoutThreshold", 1161 lockoutThreshold, "MaxPasswordLength", 1162 maxPasswordLength, "MinPasswordLength", 1163 minPasswordLength, "LDAP", ldapObject, 1164 "ActiveDirectory", activeDirectoryObject)) 1165 { 1166 return; 1167 } 1168 1169 if (minPasswordLength) 1170 { 1171 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 1172 } 1173 1174 if (maxPasswordLength) 1175 { 1176 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1177 } 1178 1179 if (ldapObject) 1180 { 1181 handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP"); 1182 } 1183 1184 if (activeDirectoryObject) 1185 { 1186 handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params, 1187 "ActiveDirectory"); 1188 } 1189 1190 if (unlockTimeout) 1191 { 1192 crow::connections::systemBus->async_method_call( 1193 [asyncResp](const boost::system::error_code ec) { 1194 if (ec) 1195 { 1196 messages::internalError(asyncResp->res); 1197 return; 1198 } 1199 messages::success(asyncResp->res); 1200 }, 1201 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1202 "org.freedesktop.DBus.Properties", "Set", 1203 "xyz.openbmc_project.User.AccountPolicy", 1204 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 1205 } 1206 if (lockoutThreshold) 1207 { 1208 crow::connections::systemBus->async_method_call( 1209 [asyncResp](const boost::system::error_code ec) { 1210 if (ec) 1211 { 1212 messages::internalError(asyncResp->res); 1213 return; 1214 } 1215 messages::success(asyncResp->res); 1216 }, 1217 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1218 "org.freedesktop.DBus.Properties", "Set", 1219 "xyz.openbmc_project.User.AccountPolicy", 1220 "MaxLoginAttemptBeforeLockout", 1221 std::variant<uint16_t>(*lockoutThreshold)); 1222 } 1223 } 1224 }; 1225 1226 class AccountsCollection : public Node 1227 { 1228 public: 1229 AccountsCollection(CrowApp& app) : 1230 Node(app, "/redfish/v1/AccountService/Accounts/") 1231 { 1232 entityPrivileges = { 1233 {boost::beast::http::verb::get, 1234 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 1235 {boost::beast::http::verb::head, {{"Login"}}}, 1236 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1237 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1238 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1239 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1240 } 1241 1242 private: 1243 void doGet(crow::Response& res, const crow::Request& req, 1244 const std::vector<std::string>& params) override 1245 { 1246 auto asyncResp = std::make_shared<AsyncResp>(res); 1247 res.jsonValue = {{"@odata.context", 1248 "/redfish/v1/" 1249 "$metadata#ManagerAccountCollection." 1250 "ManagerAccountCollection"}, 1251 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 1252 {"@odata.type", "#ManagerAccountCollection." 1253 "ManagerAccountCollection"}, 1254 {"Name", "Accounts Collection"}, 1255 {"Description", "BMC User Accounts"}}; 1256 1257 crow::connections::systemBus->async_method_call( 1258 [asyncResp](const boost::system::error_code ec, 1259 const ManagedObjectType& users) { 1260 if (ec) 1261 { 1262 messages::internalError(asyncResp->res); 1263 return; 1264 } 1265 1266 nlohmann::json& memberArray = 1267 asyncResp->res.jsonValue["Members"]; 1268 memberArray = nlohmann::json::array(); 1269 1270 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 1271 for (auto& user : users) 1272 { 1273 const std::string& path = 1274 static_cast<const std::string&>(user.first); 1275 std::size_t lastIndex = path.rfind("/"); 1276 if (lastIndex == std::string::npos) 1277 { 1278 lastIndex = 0; 1279 } 1280 else 1281 { 1282 lastIndex += 1; 1283 } 1284 memberArray.push_back( 1285 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 1286 path.substr(lastIndex)}}); 1287 } 1288 }, 1289 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1290 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1291 } 1292 void doPost(crow::Response& res, const crow::Request& req, 1293 const std::vector<std::string>& params) override 1294 { 1295 auto asyncResp = std::make_shared<AsyncResp>(res); 1296 1297 std::string username; 1298 std::string password; 1299 std::optional<std::string> roleId("User"); 1300 std::optional<bool> enabled = true; 1301 if (!json_util::readJson(req, res, "UserName", username, "Password", 1302 password, "RoleId", roleId, "Enabled", 1303 enabled)) 1304 { 1305 return; 1306 } 1307 1308 std::string priv = getPrivilegeFromRoleId(*roleId); 1309 if (priv.empty()) 1310 { 1311 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1312 return; 1313 } 1314 roleId = priv; 1315 1316 // Reading AllGroups property 1317 crow::connections::systemBus->async_method_call( 1318 [asyncResp, username, password{std::move(password)}, roleId, 1319 enabled](const boost::system::error_code ec, 1320 const std::variant<std::vector<std::string>>& allGroups) { 1321 if (ec) 1322 { 1323 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1324 messages::internalError(asyncResp->res); 1325 return; 1326 } 1327 1328 const std::vector<std::string>* allGroupsList = 1329 std::get_if<std::vector<std::string>>(&allGroups); 1330 1331 if (allGroupsList == nullptr || allGroupsList->empty()) 1332 { 1333 messages::internalError(asyncResp->res); 1334 return; 1335 } 1336 1337 crow::connections::systemBus->async_method_call( 1338 [asyncResp, username, password{std::move(password)}]( 1339 const boost::system::error_code ec) { 1340 if (ec) 1341 { 1342 messages::resourceAlreadyExists( 1343 asyncResp->res, 1344 "#ManagerAccount.v1_0_3.ManagerAccount", 1345 "UserName", username); 1346 return; 1347 } 1348 1349 if (pamUpdatePassword(username, password) != 1350 PAM_SUCCESS) 1351 { 1352 // At this point we have a user that's been created, 1353 // but the password set failed.Something is wrong, 1354 // so delete the user that we've already created 1355 crow::connections::systemBus->async_method_call( 1356 [asyncResp]( 1357 const boost::system::error_code ec) { 1358 if (ec) 1359 { 1360 messages::internalError(asyncResp->res); 1361 return; 1362 } 1363 1364 messages::invalidObject(asyncResp->res, 1365 "Password"); 1366 }, 1367 "xyz.openbmc_project.User.Manager", 1368 "/xyz/openbmc_project/user/" + username, 1369 "xyz.openbmc_project.Object.Delete", "Delete"); 1370 1371 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1372 return; 1373 } 1374 1375 messages::created(asyncResp->res); 1376 asyncResp->res.addHeader( 1377 "Location", 1378 "/redfish/v1/AccountService/Accounts/" + username); 1379 }, 1380 "xyz.openbmc_project.User.Manager", 1381 "/xyz/openbmc_project/user", 1382 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1383 *allGroupsList, *roleId, *enabled); 1384 }, 1385 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1386 "org.freedesktop.DBus.Properties", "Get", 1387 "xyz.openbmc_project.User.Manager", "AllGroups"); 1388 } 1389 }; 1390 1391 class ManagerAccount : public Node 1392 { 1393 public: 1394 ManagerAccount(CrowApp& app) : 1395 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1396 { 1397 entityPrivileges = { 1398 {boost::beast::http::verb::get, 1399 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1400 {boost::beast::http::verb::head, {{"Login"}}}, 1401 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1402 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1403 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1404 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1405 } 1406 1407 private: 1408 void doGet(crow::Response& res, const crow::Request& req, 1409 const std::vector<std::string>& params) override 1410 { 1411 1412 auto asyncResp = std::make_shared<AsyncResp>(res); 1413 1414 if (params.size() != 1) 1415 { 1416 messages::internalError(asyncResp->res); 1417 return; 1418 } 1419 1420 crow::connections::systemBus->async_method_call( 1421 [asyncResp, accountName{std::string(params[0])}]( 1422 const boost::system::error_code ec, 1423 const ManagedObjectType& users) { 1424 if (ec) 1425 { 1426 messages::internalError(asyncResp->res); 1427 return; 1428 } 1429 auto userIt = users.begin(); 1430 1431 for (; userIt != users.end(); userIt++) 1432 { 1433 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1434 { 1435 break; 1436 } 1437 } 1438 if (userIt == users.end()) 1439 { 1440 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1441 accountName); 1442 return; 1443 } 1444 1445 asyncResp->res.jsonValue = { 1446 {"@odata.context", 1447 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 1448 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 1449 {"Name", "User Account"}, 1450 {"Description", "User Account"}, 1451 {"Password", nullptr}}; 1452 1453 for (const auto& interface : userIt->second) 1454 { 1455 if (interface.first == 1456 "xyz.openbmc_project.User.Attributes") 1457 { 1458 for (const auto& property : interface.second) 1459 { 1460 if (property.first == "UserEnabled") 1461 { 1462 const bool* userEnabled = 1463 std::get_if<bool>(&property.second); 1464 if (userEnabled == nullptr) 1465 { 1466 BMCWEB_LOG_ERROR 1467 << "UserEnabled wasn't a bool"; 1468 messages::internalError(asyncResp->res); 1469 return; 1470 } 1471 asyncResp->res.jsonValue["Enabled"] = 1472 *userEnabled; 1473 } 1474 else if (property.first == 1475 "UserLockedForFailedAttempt") 1476 { 1477 const bool* userLocked = 1478 std::get_if<bool>(&property.second); 1479 if (userLocked == nullptr) 1480 { 1481 BMCWEB_LOG_ERROR << "UserLockedForF" 1482 "ailedAttempt " 1483 "wasn't a bool"; 1484 messages::internalError(asyncResp->res); 1485 return; 1486 } 1487 asyncResp->res.jsonValue["Locked"] = 1488 *userLocked; 1489 asyncResp->res.jsonValue 1490 ["Locked@Redfish.AllowableValues"] = { 1491 "false"}; 1492 } 1493 else if (property.first == "UserPrivilege") 1494 { 1495 const std::string* userPrivPtr = 1496 std::get_if<std::string>(&property.second); 1497 if (userPrivPtr == nullptr) 1498 { 1499 BMCWEB_LOG_ERROR 1500 << "UserPrivilege wasn't a " 1501 "string"; 1502 messages::internalError(asyncResp->res); 1503 return; 1504 } 1505 std::string role = 1506 getRoleIdFromPrivilege(*userPrivPtr); 1507 if (role.empty()) 1508 { 1509 BMCWEB_LOG_ERROR << "Invalid user role"; 1510 messages::internalError(asyncResp->res); 1511 return; 1512 } 1513 asyncResp->res.jsonValue["RoleId"] = role; 1514 1515 asyncResp->res.jsonValue["Links"]["Role"] = { 1516 {"@odata.id", "/redfish/v1/AccountService/" 1517 "Roles/" + 1518 role}}; 1519 } 1520 } 1521 } 1522 } 1523 1524 asyncResp->res.jsonValue["@odata.id"] = 1525 "/redfish/v1/AccountService/Accounts/" + accountName; 1526 asyncResp->res.jsonValue["Id"] = accountName; 1527 asyncResp->res.jsonValue["UserName"] = accountName; 1528 }, 1529 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1530 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1531 } 1532 1533 void doPatch(crow::Response& res, const crow::Request& req, 1534 const std::vector<std::string>& params) override 1535 { 1536 auto asyncResp = std::make_shared<AsyncResp>(res); 1537 if (params.size() != 1) 1538 { 1539 messages::internalError(asyncResp->res); 1540 return; 1541 } 1542 1543 std::optional<std::string> newUserName; 1544 std::optional<std::string> password; 1545 std::optional<bool> enabled; 1546 std::optional<std::string> roleId; 1547 std::optional<bool> locked; 1548 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1549 password, "RoleId", roleId, "Enabled", enabled, 1550 "Locked", locked)) 1551 { 1552 return; 1553 } 1554 1555 const std::string& username = params[0]; 1556 1557 // if user name is not provided in the patch method or if it 1558 // matches the user name in the URI, then we are treating it as updating 1559 // user properties other then username. If username provided doesn't 1560 // match the URI, then we are treating this as user rename request. 1561 if (!newUserName || (newUserName.value() == username)) 1562 { 1563 updateUserProperties(asyncResp, username, password, enabled, roleId, 1564 locked); 1565 return; 1566 } 1567 else 1568 { 1569 crow::connections::systemBus->async_method_call( 1570 [this, asyncResp, username, password(std::move(password)), 1571 roleId(std::move(roleId)), enabled(std::move(enabled)), 1572 newUser{std::string(*newUserName)}, 1573 locked(std::move(locked))](const boost::system::error_code ec, 1574 sdbusplus::message::message& m) { 1575 if (ec) 1576 { 1577 userErrorMessageHandler(m.get_error(), asyncResp, 1578 newUser, username); 1579 return; 1580 } 1581 1582 updateUserProperties(asyncResp, newUser, password, enabled, 1583 roleId, locked); 1584 }, 1585 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1586 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1587 *newUserName); 1588 } 1589 } 1590 1591 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1592 const std::string& username, 1593 std::optional<std::string> password, 1594 std::optional<bool> enabled, 1595 std::optional<std::string> roleId, 1596 std::optional<bool> locked) 1597 { 1598 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1599 dbus::utility::escapePathForDbus(dbusObjectPath); 1600 1601 dbus::utility::checkDbusPathExists( 1602 dbusObjectPath, 1603 [dbusObjectPath(std::move(dbusObjectPath)), username, 1604 password(std::move(password)), roleId(std::move(roleId)), 1605 enabled(std::move(enabled)), locked(std::move(locked)), 1606 asyncResp{std::move(asyncResp)}](int rc) { 1607 if (!rc) 1608 { 1609 messages::resourceNotFound( 1610 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1611 username); 1612 return; 1613 } 1614 1615 if (password) 1616 { 1617 int retval = pamUpdatePassword(username, *password); 1618 1619 if (retval == PAM_USER_UNKNOWN) 1620 { 1621 messages::resourceNotFound( 1622 asyncResp->res, 1623 "#ManagerAccount.v1_0_3.ManagerAccount", username); 1624 } 1625 else if (retval == PAM_AUTHTOK_ERR) 1626 { 1627 // If password is invalid 1628 messages::propertyValueFormatError( 1629 asyncResp->res, *password, "Password"); 1630 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1631 } 1632 else if (retval != PAM_SUCCESS) 1633 { 1634 messages::internalError(asyncResp->res); 1635 return; 1636 } 1637 } 1638 1639 if (enabled) 1640 { 1641 crow::connections::systemBus->async_method_call( 1642 [asyncResp](const boost::system::error_code ec) { 1643 if (ec) 1644 { 1645 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1646 << ec; 1647 messages::internalError(asyncResp->res); 1648 return; 1649 } 1650 messages::success(asyncResp->res); 1651 return; 1652 }, 1653 "xyz.openbmc_project.User.Manager", 1654 dbusObjectPath.c_str(), 1655 "org.freedesktop.DBus.Properties", "Set", 1656 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1657 std::variant<bool>{*enabled}); 1658 } 1659 1660 if (roleId) 1661 { 1662 std::string priv = getPrivilegeFromRoleId(*roleId); 1663 if (priv.empty()) 1664 { 1665 messages::propertyValueNotInList(asyncResp->res, 1666 *roleId, "RoleId"); 1667 return; 1668 } 1669 1670 crow::connections::systemBus->async_method_call( 1671 [asyncResp](const boost::system::error_code ec) { 1672 if (ec) 1673 { 1674 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1675 << ec; 1676 messages::internalError(asyncResp->res); 1677 return; 1678 } 1679 messages::success(asyncResp->res); 1680 }, 1681 "xyz.openbmc_project.User.Manager", 1682 dbusObjectPath.c_str(), 1683 "org.freedesktop.DBus.Properties", "Set", 1684 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1685 std::variant<std::string>{priv}); 1686 } 1687 1688 if (locked) 1689 { 1690 // admin can unlock the account which is locked by 1691 // successive authentication failures but admin should 1692 // not be allowed to lock an account. 1693 if (*locked) 1694 { 1695 messages::propertyValueNotInList(asyncResp->res, "true", 1696 "Locked"); 1697 return; 1698 } 1699 1700 crow::connections::systemBus->async_method_call( 1701 [asyncResp](const boost::system::error_code ec) { 1702 if (ec) 1703 { 1704 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1705 << ec; 1706 messages::internalError(asyncResp->res); 1707 return; 1708 } 1709 messages::success(asyncResp->res); 1710 return; 1711 }, 1712 "xyz.openbmc_project.User.Manager", 1713 dbusObjectPath.c_str(), 1714 "org.freedesktop.DBus.Properties", "Set", 1715 "xyz.openbmc_project.User.Attributes", 1716 "UserLockedForFailedAttempt", 1717 sdbusplus::message::variant<bool>{*locked}); 1718 } 1719 }); 1720 } 1721 1722 void doDelete(crow::Response& res, const crow::Request& req, 1723 const std::vector<std::string>& params) override 1724 { 1725 auto asyncResp = std::make_shared<AsyncResp>(res); 1726 1727 if (params.size() != 1) 1728 { 1729 messages::internalError(asyncResp->res); 1730 return; 1731 } 1732 1733 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1734 1735 crow::connections::systemBus->async_method_call( 1736 [asyncResp, username{std::move(params[0])}]( 1737 const boost::system::error_code ec) { 1738 if (ec) 1739 { 1740 messages::resourceNotFound( 1741 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1742 username); 1743 return; 1744 } 1745 1746 messages::accountRemoved(asyncResp->res); 1747 }, 1748 "xyz.openbmc_project.User.Manager", userPath, 1749 "xyz.openbmc_project.Object.Delete", "Delete"); 1750 } 1751 }; 1752 1753 } // namespace redfish 1754