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