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 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.emplace_back( 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) : 509 Node(app, "/redfish/v1/AccountService/"), app(app) 510 { 511 entityPrivileges = { 512 {boost::beast::http::verb::get, 513 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 514 {boost::beast::http::verb::head, {{"Login"}}}, 515 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 516 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 517 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 518 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 519 } 520 521 private: 522 /** 523 * @brief parses the authentication section under the LDAP 524 * @param input JSON data 525 * @param asyncResp pointer to the JSON response 526 * @param userName userName to be filled from the given JSON. 527 * @param password password to be filled from the given JSON. 528 */ 529 void 530 parseLDAPAuthenticationJson(nlohmann::json input, 531 const std::shared_ptr<AsyncResp>& asyncResp, 532 std::optional<std::string>& username, 533 std::optional<std::string>& password) 534 { 535 std::optional<std::string> authType; 536 537 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 538 authType, "Username", username, "Password", 539 password)) 540 { 541 return; 542 } 543 if (!authType) 544 { 545 return; 546 } 547 if (*authType != "UsernameAndPassword") 548 { 549 messages::propertyValueNotInList(asyncResp->res, *authType, 550 "AuthenticationType"); 551 return; 552 } 553 } 554 /** 555 * @brief parses the LDAPService section under the LDAP 556 * @param input JSON data 557 * @param asyncResp pointer to the JSON response 558 * @param baseDNList baseDN to be filled from the given JSON. 559 * @param userNameAttribute userName to be filled from the given JSON. 560 * @param groupaAttribute password to be filled from the given JSON. 561 */ 562 563 void parseLDAPServiceJson( 564 nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp, 565 std::optional<std::vector<std::string>>& baseDNList, 566 std::optional<std::string>& userNameAttribute, 567 std::optional<std::string>& groupsAttribute) 568 { 569 std::optional<nlohmann::json> searchSettings; 570 571 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 572 searchSettings)) 573 { 574 return; 575 } 576 if (!searchSettings) 577 { 578 return; 579 } 580 if (!json_util::readJson(*searchSettings, asyncResp->res, 581 "BaseDistinguishedNames", baseDNList, 582 "UsernameAttribute", userNameAttribute, 583 "GroupsAttribute", groupsAttribute)) 584 { 585 return; 586 } 587 } 588 /** 589 * @brief updates the LDAP server address and updates the 590 json response with the new value. 591 * @param serviceAddressList address to be updated. 592 * @param asyncResp pointer to the JSON response 593 * @param ldapServerElementName Type of LDAP 594 server(openLDAP/ActiveDirectory) 595 */ 596 597 void handleServiceAddressPatch( 598 const std::vector<std::string>& serviceAddressList, 599 const std::shared_ptr<AsyncResp>& asyncResp, 600 const std::string& ldapServerElementName, 601 const std::string& ldapConfigObject) 602 { 603 crow::connections::systemBus->async_method_call( 604 [asyncResp, ldapServerElementName, 605 serviceAddressList](const boost::system::error_code ec) { 606 if (ec) 607 { 608 BMCWEB_LOG_DEBUG 609 << "Error Occured in updating the service address"; 610 messages::internalError(asyncResp->res); 611 return; 612 } 613 std::vector<std::string> modifiedserviceAddressList = { 614 serviceAddressList.front()}; 615 asyncResp->res 616 .jsonValue[ldapServerElementName]["ServiceAddresses"] = 617 modifiedserviceAddressList; 618 if ((serviceAddressList).size() > 1) 619 { 620 messages::propertyValueModified(asyncResp->res, 621 "ServiceAddresses", 622 serviceAddressList.front()); 623 } 624 BMCWEB_LOG_DEBUG << "Updated the service address"; 625 }, 626 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 627 ldapConfigInterface, "LDAPServerURI", 628 std::variant<std::string>(serviceAddressList.front())); 629 } 630 /** 631 * @brief updates the LDAP Bind DN and updates the 632 json response with the new value. 633 * @param username name of the user which needs to be updated. 634 * @param asyncResp pointer to the JSON response 635 * @param ldapServerElementName Type of LDAP 636 server(openLDAP/ActiveDirectory) 637 */ 638 639 void handleUserNamePatch(const std::string& username, 640 const std::shared_ptr<AsyncResp>& asyncResp, 641 const std::string& ldapServerElementName, 642 const std::string& ldapConfigObject) 643 { 644 crow::connections::systemBus->async_method_call( 645 [asyncResp, username, 646 ldapServerElementName](const boost::system::error_code ec) { 647 if (ec) 648 { 649 BMCWEB_LOG_DEBUG 650 << "Error occured in updating the username"; 651 messages::internalError(asyncResp->res); 652 return; 653 } 654 asyncResp->res.jsonValue[ldapServerElementName] 655 ["Authentication"]["Username"] = 656 username; 657 BMCWEB_LOG_DEBUG << "Updated the username"; 658 }, 659 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 660 ldapConfigInterface, "LDAPBindDN", 661 std::variant<std::string>(username)); 662 } 663 664 /** 665 * @brief updates the LDAP password 666 * @param password : ldap password which needs to be updated. 667 * @param asyncResp pointer to the JSON response 668 * @param ldapServerElementName Type of LDAP 669 * server(openLDAP/ActiveDirectory) 670 */ 671 672 void handlePasswordPatch(const std::string& password, 673 const std::shared_ptr<AsyncResp>& asyncResp, 674 const std::string& ldapServerElementName, 675 const std::string& ldapConfigObject) 676 { 677 crow::connections::systemBus->async_method_call( 678 [asyncResp, password, 679 ldapServerElementName](const boost::system::error_code ec) { 680 if (ec) 681 { 682 BMCWEB_LOG_DEBUG 683 << "Error occured in updating the password"; 684 messages::internalError(asyncResp->res); 685 return; 686 } 687 asyncResp->res.jsonValue[ldapServerElementName] 688 ["Authentication"]["Password"] = ""; 689 BMCWEB_LOG_DEBUG << "Updated the password"; 690 }, 691 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 692 ldapConfigInterface, "LDAPBindDNPassword", 693 std::variant<std::string>(password)); 694 } 695 696 /** 697 * @brief updates the LDAP BaseDN and updates the 698 json response with the new value. 699 * @param baseDNList baseDN list which needs to be updated. 700 * @param asyncResp pointer to the JSON response 701 * @param ldapServerElementName Type of LDAP 702 server(openLDAP/ActiveDirectory) 703 */ 704 705 void handleBaseDNPatch(const std::vector<std::string>& baseDNList, 706 const std::shared_ptr<AsyncResp>& asyncResp, 707 const std::string& ldapServerElementName, 708 const std::string& ldapConfigObject) 709 { 710 crow::connections::systemBus->async_method_call( 711 [asyncResp, baseDNList, 712 ldapServerElementName](const boost::system::error_code ec) { 713 if (ec) 714 { 715 BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN"; 716 messages::internalError(asyncResp->res); 717 return; 718 } 719 auto& serverTypeJson = 720 asyncResp->res.jsonValue[ldapServerElementName]; 721 auto& searchSettingsJson = 722 serverTypeJson["LDAPService"]["SearchSettings"]; 723 std::vector<std::string> modifiedBaseDNList = { 724 baseDNList.front()}; 725 searchSettingsJson["BaseDistinguishedNames"] = 726 modifiedBaseDNList; 727 if (baseDNList.size() > 1) 728 { 729 messages::propertyValueModified(asyncResp->res, 730 "BaseDistinguishedNames", 731 baseDNList.front()); 732 } 733 BMCWEB_LOG_DEBUG << "Updated the base DN"; 734 }, 735 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 736 ldapConfigInterface, "LDAPBaseDN", 737 std::variant<std::string>(baseDNList.front())); 738 } 739 /** 740 * @brief updates the LDAP user name attribute and updates the 741 json response with the new value. 742 * @param userNameAttribute attribute to be updated. 743 * @param asyncResp pointer to the JSON response 744 * @param ldapServerElementName Type of LDAP 745 server(openLDAP/ActiveDirectory) 746 */ 747 748 void handleUserNameAttrPatch(const std::string& userNameAttribute, 749 const std::shared_ptr<AsyncResp>& asyncResp, 750 const std::string& ldapServerElementName, 751 const std::string& ldapConfigObject) 752 { 753 crow::connections::systemBus->async_method_call( 754 [asyncResp, userNameAttribute, 755 ldapServerElementName](const boost::system::error_code ec) { 756 if (ec) 757 { 758 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 759 "username attribute"; 760 messages::internalError(asyncResp->res); 761 return; 762 } 763 auto& serverTypeJson = 764 asyncResp->res.jsonValue[ldapServerElementName]; 765 auto& searchSettingsJson = 766 serverTypeJson["LDAPService"]["SearchSettings"]; 767 searchSettingsJson["UsernameAttribute"] = userNameAttribute; 768 BMCWEB_LOG_DEBUG << "Updated the user name attr."; 769 }, 770 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 771 ldapConfigInterface, "UserNameAttribute", 772 std::variant<std::string>(userNameAttribute)); 773 } 774 /** 775 * @brief updates the LDAP group attribute and updates the 776 json response with the new value. 777 * @param groupsAttribute attribute to be updated. 778 * @param asyncResp pointer to the JSON response 779 * @param ldapServerElementName Type of LDAP 780 server(openLDAP/ActiveDirectory) 781 */ 782 783 void handleGroupNameAttrPatch(const std::string& groupsAttribute, 784 const std::shared_ptr<AsyncResp>& asyncResp, 785 const std::string& ldapServerElementName, 786 const std::string& ldapConfigObject) 787 { 788 crow::connections::systemBus->async_method_call( 789 [asyncResp, groupsAttribute, 790 ldapServerElementName](const boost::system::error_code ec) { 791 if (ec) 792 { 793 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 794 "groupname attribute"; 795 messages::internalError(asyncResp->res); 796 return; 797 } 798 auto& serverTypeJson = 799 asyncResp->res.jsonValue[ldapServerElementName]; 800 auto& searchSettingsJson = 801 serverTypeJson["LDAPService"]["SearchSettings"]; 802 searchSettingsJson["GroupsAttribute"] = groupsAttribute; 803 BMCWEB_LOG_DEBUG << "Updated the groupname attr"; 804 }, 805 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 806 ldapConfigInterface, "GroupNameAttribute", 807 std::variant<std::string>(groupsAttribute)); 808 } 809 /** 810 * @brief updates the LDAP service enable and updates the 811 json response with the new value. 812 * @param input JSON data. 813 * @param asyncResp pointer to the JSON response 814 * @param ldapServerElementName Type of LDAP 815 server(openLDAP/ActiveDirectory) 816 */ 817 818 void handleServiceEnablePatch(bool serviceEnabled, 819 const std::shared_ptr<AsyncResp>& asyncResp, 820 const std::string& ldapServerElementName, 821 const std::string& ldapConfigObject) 822 { 823 crow::connections::systemBus->async_method_call( 824 [asyncResp, serviceEnabled, 825 ldapServerElementName](const boost::system::error_code ec) { 826 if (ec) 827 { 828 BMCWEB_LOG_DEBUG 829 << "Error Occured in Updating the service enable"; 830 messages::internalError(asyncResp->res); 831 return; 832 } 833 asyncResp->res 834 .jsonValue[ldapServerElementName]["ServiceEnabled"] = 835 serviceEnabled; 836 BMCWEB_LOG_DEBUG << "Updated Service enable = " 837 << serviceEnabled; 838 }, 839 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 840 ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled)); 841 } 842 843 void handleAuthMethodsPatch(nlohmann::json& input, 844 const std::shared_ptr<AsyncResp>& asyncResp) 845 { 846 std::optional<bool> basicAuth; 847 std::optional<bool> cookie; 848 std::optional<bool> sessionToken; 849 std::optional<bool> xToken; 850 851 if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth, 852 "Cookie", cookie, "SessionToken", sessionToken, 853 "XToken", xToken)) 854 { 855 BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag"; 856 return; 857 } 858 859 // Make a copy of methods configuration 860 crow::persistent_data::AuthConfigMethods authMethodsConfig = 861 crow::persistent_data::SessionStore::getInstance() 862 .getAuthMethodsConfig(); 863 864 if (basicAuth) 865 { 866 authMethodsConfig.basic = *basicAuth; 867 } 868 869 if (cookie) 870 { 871 authMethodsConfig.cookie = *cookie; 872 } 873 874 if (sessionToken) 875 { 876 authMethodsConfig.sessionToken = *sessionToken; 877 } 878 879 if (xToken) 880 { 881 authMethodsConfig.xtoken = *xToken; 882 } 883 884 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 885 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken) 886 { 887 // Do not allow user to disable everything 888 messages::actionNotSupported(asyncResp->res, 889 "of disabling all available methods"); 890 return; 891 } 892 893 crow::persistent_data::SessionStore::getInstance() 894 .updateAuthMethodsConfig(authMethodsConfig); 895 // Save configuration immediately 896 app.template getMiddleware<crow::persistent_data::Middleware>() 897 .writeData(); 898 899 messages::success(asyncResp->res); 900 } 901 902 /** 903 * @brief Get the required values from the given JSON, validates the 904 * value and create the LDAP config object. 905 * @param input JSON data 906 * @param asyncResp pointer to the JSON response 907 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 908 */ 909 910 void handleLDAPPatch(nlohmann::json& input, 911 const std::shared_ptr<AsyncResp>& asyncResp, 912 const crow::Request& req, 913 const std::vector<std::string>& params, 914 const std::string& serverType) 915 { 916 std::string dbusObjectPath; 917 if (serverType == "ActiveDirectory") 918 { 919 dbusObjectPath = ADConfigObject; 920 } 921 else if (serverType == "LDAP") 922 { 923 dbusObjectPath = ldapConfigObject; 924 } 925 926 std::optional<nlohmann::json> authentication; 927 std::optional<nlohmann::json> ldapService; 928 std::optional<std::string> accountProviderType; 929 std::optional<std::vector<std::string>> serviceAddressList; 930 std::optional<bool> serviceEnabled; 931 std::optional<std::vector<std::string>> baseDNList; 932 std::optional<std::string> userNameAttribute; 933 std::optional<std::string> groupsAttribute; 934 std::optional<std::string> userName; 935 std::optional<std::string> password; 936 std::optional<std::vector<nlohmann::json>> remoteRoleMapData; 937 938 if (!json_util::readJson(input, asyncResp->res, "Authentication", 939 authentication, "LDAPService", ldapService, 940 "ServiceAddresses", serviceAddressList, 941 "AccountProviderType", accountProviderType, 942 "ServiceEnabled", serviceEnabled, 943 "RemoteRoleMapping", remoteRoleMapData)) 944 { 945 return; 946 } 947 948 if (authentication) 949 { 950 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 951 password); 952 } 953 if (ldapService) 954 { 955 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 956 userNameAttribute, groupsAttribute); 957 } 958 if (accountProviderType) 959 { 960 messages::propertyNotWritable(asyncResp->res, 961 "AccountProviderType"); 962 } 963 if (serviceAddressList) 964 { 965 if ((*serviceAddressList).size() == 0) 966 { 967 messages::propertyValueNotInList(asyncResp->res, "[]", 968 "ServiceAddress"); 969 return; 970 } 971 } 972 if (baseDNList) 973 { 974 if ((*baseDNList).size() == 0) 975 { 976 messages::propertyValueNotInList(asyncResp->res, "[]", 977 "BaseDistinguishedNames"); 978 return; 979 } 980 } 981 982 // nothing to update, then return 983 if (!userName && !password && !serviceAddressList && !baseDNList && 984 !userNameAttribute && !groupsAttribute && !serviceEnabled && 985 !remoteRoleMapData) 986 { 987 return; 988 } 989 990 // Get the existing resource first then keep modifying 991 // whenever any property gets updated. 992 getLDAPConfigData(serverType, [this, asyncResp, userName, password, 993 baseDNList, userNameAttribute, 994 groupsAttribute, accountProviderType, 995 serviceAddressList, serviceEnabled, 996 dbusObjectPath, remoteRoleMapData]( 997 bool success, LDAPConfigData confData, 998 const std::string& serverType) { 999 if (!success) 1000 { 1001 messages::internalError(asyncResp->res); 1002 return; 1003 } 1004 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType); 1005 if (confData.serviceEnabled) 1006 { 1007 // Disable the service first and update the rest of 1008 // the properties. 1009 handleServiceEnablePatch(false, asyncResp, serverType, 1010 dbusObjectPath); 1011 } 1012 1013 if (serviceAddressList) 1014 { 1015 handleServiceAddressPatch(*serviceAddressList, asyncResp, 1016 serverType, dbusObjectPath); 1017 } 1018 if (userName) 1019 { 1020 handleUserNamePatch(*userName, asyncResp, serverType, 1021 dbusObjectPath); 1022 } 1023 if (password) 1024 { 1025 handlePasswordPatch(*password, asyncResp, serverType, 1026 dbusObjectPath); 1027 } 1028 1029 if (baseDNList) 1030 { 1031 handleBaseDNPatch(*baseDNList, asyncResp, serverType, 1032 dbusObjectPath); 1033 } 1034 if (userNameAttribute) 1035 { 1036 handleUserNameAttrPatch(*userNameAttribute, asyncResp, 1037 serverType, dbusObjectPath); 1038 } 1039 if (groupsAttribute) 1040 { 1041 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, 1042 serverType, dbusObjectPath); 1043 } 1044 if (serviceEnabled) 1045 { 1046 // if user has given the value as true then enable 1047 // the service. if user has given false then no-op 1048 // as service is already stopped. 1049 if (*serviceEnabled) 1050 { 1051 handleServiceEnablePatch(*serviceEnabled, asyncResp, 1052 serverType, dbusObjectPath); 1053 } 1054 } 1055 else 1056 { 1057 // if user has not given the service enabled value 1058 // then revert it to the same state as it was 1059 // before. 1060 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1061 serverType, dbusObjectPath); 1062 } 1063 1064 if (remoteRoleMapData) 1065 { 1066 std::vector<nlohmann::json> remoteRoleMap = 1067 std::move(*remoteRoleMapData); 1068 1069 handleRoleMapPatch(asyncResp, confData.groupRoleList, 1070 serverType, remoteRoleMap); 1071 } 1072 }); 1073 } 1074 1075 void doGet(crow::Response& res, const crow::Request& req, 1076 const std::vector<std::string>& params) override 1077 { 1078 const crow::persistent_data::AuthConfigMethods& authMethodsConfig = 1079 crow::persistent_data::SessionStore::getInstance() 1080 .getAuthMethodsConfig(); 1081 1082 auto asyncResp = std::make_shared<AsyncResp>(res); 1083 res.jsonValue = { 1084 {"@odata.context", "/redfish/v1/" 1085 "$metadata#AccountService.AccountService"}, 1086 {"@odata.id", "/redfish/v1/AccountService"}, 1087 {"@odata.type", "#AccountService." 1088 "v1_4_0.AccountService"}, 1089 {"Id", "AccountService"}, 1090 {"Name", "Account Service"}, 1091 {"Description", "Account Service"}, 1092 {"ServiceEnabled", true}, 1093 {"MaxPasswordLength", 20}, 1094 {"Accounts", 1095 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 1096 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}, 1097 {"Oem", 1098 {{"OpenBMC", 1099 {{"@odata.type", "#OemAccountService.v1_0_0.AccountService"}, 1100 {"AuthMethods", 1101 { 1102 {"BasicAuth", authMethodsConfig.basic}, 1103 {"SessionToken", authMethodsConfig.sessionToken}, 1104 {"XToken", authMethodsConfig.xtoken}, 1105 {"Cookie", authMethodsConfig.cookie}, 1106 }}}}}}, 1107 {"LDAP", 1108 {{"Certificates", 1109 {{"@odata.id", 1110 "/redfish/v1/AccountService/LDAP/Certificates"}}}}}}; 1111 crow::connections::systemBus->async_method_call( 1112 [asyncResp]( 1113 const boost::system::error_code ec, 1114 const std::vector<std::pair< 1115 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 1116 propertiesList) { 1117 if (ec) 1118 { 1119 messages::internalError(asyncResp->res); 1120 return; 1121 } 1122 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1123 << "properties for AccountService"; 1124 for (const std::pair<std::string, 1125 std::variant<uint32_t, uint16_t, uint8_t>>& 1126 property : propertiesList) 1127 { 1128 if (property.first == "MinPasswordLength") 1129 { 1130 const uint8_t* value = 1131 std::get_if<uint8_t>(&property.second); 1132 if (value != nullptr) 1133 { 1134 asyncResp->res.jsonValue["MinPasswordLength"] = 1135 *value; 1136 } 1137 } 1138 if (property.first == "AccountUnlockTimeout") 1139 { 1140 const uint32_t* value = 1141 std::get_if<uint32_t>(&property.second); 1142 if (value != nullptr) 1143 { 1144 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1145 *value; 1146 } 1147 } 1148 if (property.first == "MaxLoginAttemptBeforeLockout") 1149 { 1150 const uint16_t* value = 1151 std::get_if<uint16_t>(&property.second); 1152 if (value != nullptr) 1153 { 1154 asyncResp->res 1155 .jsonValue["AccountLockoutThreshold"] = *value; 1156 } 1157 } 1158 } 1159 }, 1160 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1161 "org.freedesktop.DBus.Properties", "GetAll", 1162 "xyz.openbmc_project.User.AccountPolicy"); 1163 1164 auto callback = [asyncResp](bool success, LDAPConfigData& confData, 1165 const std::string& ldapType) { 1166 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1167 }; 1168 1169 getLDAPConfigData("LDAP", callback); 1170 getLDAPConfigData("ActiveDirectory", callback); 1171 } 1172 1173 void doPatch(crow::Response& res, const crow::Request& req, 1174 const std::vector<std::string>& params) override 1175 { 1176 auto asyncResp = std::make_shared<AsyncResp>(res); 1177 1178 std::optional<uint32_t> unlockTimeout; 1179 std::optional<uint16_t> lockoutThreshold; 1180 std::optional<uint16_t> minPasswordLength; 1181 std::optional<uint16_t> maxPasswordLength; 1182 std::optional<nlohmann::json> ldapObject; 1183 std::optional<nlohmann::json> activeDirectoryObject; 1184 std::optional<nlohmann::json> oemObject; 1185 1186 if (!json_util::readJson( 1187 req, res, "AccountLockoutDuration", unlockTimeout, 1188 "AccountLockoutThreshold", lockoutThreshold, 1189 "MaxPasswordLength", maxPasswordLength, "MinPasswordLength", 1190 minPasswordLength, "LDAP", ldapObject, "ActiveDirectory", 1191 activeDirectoryObject, "Oem", oemObject)) 1192 { 1193 return; 1194 } 1195 1196 if (minPasswordLength) 1197 { 1198 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 1199 } 1200 1201 if (maxPasswordLength) 1202 { 1203 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1204 } 1205 1206 if (ldapObject) 1207 { 1208 handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP"); 1209 } 1210 1211 if (std::optional<nlohmann::json> oemOpenBMCObject; 1212 oemObject && 1213 json_util::readJson(*oemObject, res, "OpenBMC", oemOpenBMCObject)) 1214 { 1215 if (std::optional<nlohmann::json> authMethodsObject; 1216 oemOpenBMCObject && 1217 json_util::readJson(*oemOpenBMCObject, res, "AuthMethods", 1218 authMethodsObject)) 1219 { 1220 if (authMethodsObject) 1221 { 1222 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1223 } 1224 } 1225 } 1226 1227 if (activeDirectoryObject) 1228 { 1229 handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params, 1230 "ActiveDirectory"); 1231 } 1232 1233 if (unlockTimeout) 1234 { 1235 crow::connections::systemBus->async_method_call( 1236 [asyncResp](const boost::system::error_code ec) { 1237 if (ec) 1238 { 1239 messages::internalError(asyncResp->res); 1240 return; 1241 } 1242 messages::success(asyncResp->res); 1243 }, 1244 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1245 "org.freedesktop.DBus.Properties", "Set", 1246 "xyz.openbmc_project.User.AccountPolicy", 1247 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 1248 } 1249 if (lockoutThreshold) 1250 { 1251 crow::connections::systemBus->async_method_call( 1252 [asyncResp](const boost::system::error_code ec) { 1253 if (ec) 1254 { 1255 messages::internalError(asyncResp->res); 1256 return; 1257 } 1258 messages::success(asyncResp->res); 1259 }, 1260 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1261 "org.freedesktop.DBus.Properties", "Set", 1262 "xyz.openbmc_project.User.AccountPolicy", 1263 "MaxLoginAttemptBeforeLockout", 1264 std::variant<uint16_t>(*lockoutThreshold)); 1265 } 1266 } 1267 1268 CrowApp& app; 1269 }; 1270 1271 class AccountsCollection : public Node 1272 { 1273 public: 1274 AccountsCollection(CrowApp& app) : 1275 Node(app, "/redfish/v1/AccountService/Accounts/") 1276 { 1277 entityPrivileges = { 1278 {boost::beast::http::verb::get, 1279 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 1280 {boost::beast::http::verb::head, {{"Login"}}}, 1281 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1282 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1283 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1284 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1285 } 1286 1287 private: 1288 void doGet(crow::Response& res, const crow::Request& req, 1289 const std::vector<std::string>& params) override 1290 { 1291 auto asyncResp = std::make_shared<AsyncResp>(res); 1292 res.jsonValue = {{"@odata.context", 1293 "/redfish/v1/" 1294 "$metadata#ManagerAccountCollection." 1295 "ManagerAccountCollection"}, 1296 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 1297 {"@odata.type", "#ManagerAccountCollection." 1298 "ManagerAccountCollection"}, 1299 {"Name", "Accounts Collection"}, 1300 {"Description", "BMC User Accounts"}}; 1301 1302 crow::connections::systemBus->async_method_call( 1303 [asyncResp](const boost::system::error_code ec, 1304 const ManagedObjectType& users) { 1305 if (ec) 1306 { 1307 messages::internalError(asyncResp->res); 1308 return; 1309 } 1310 1311 nlohmann::json& memberArray = 1312 asyncResp->res.jsonValue["Members"]; 1313 memberArray = nlohmann::json::array(); 1314 1315 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 1316 for (auto& user : users) 1317 { 1318 const std::string& path = 1319 static_cast<const std::string&>(user.first); 1320 std::size_t lastIndex = path.rfind("/"); 1321 if (lastIndex == std::string::npos) 1322 { 1323 lastIndex = 0; 1324 } 1325 else 1326 { 1327 lastIndex += 1; 1328 } 1329 memberArray.push_back( 1330 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 1331 path.substr(lastIndex)}}); 1332 } 1333 }, 1334 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1335 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1336 } 1337 void doPost(crow::Response& res, const crow::Request& req, 1338 const std::vector<std::string>& params) override 1339 { 1340 auto asyncResp = std::make_shared<AsyncResp>(res); 1341 1342 std::string username; 1343 std::string password; 1344 std::optional<std::string> roleId("User"); 1345 std::optional<bool> enabled = true; 1346 if (!json_util::readJson(req, res, "UserName", username, "Password", 1347 password, "RoleId", roleId, "Enabled", 1348 enabled)) 1349 { 1350 return; 1351 } 1352 1353 std::string priv = getPrivilegeFromRoleId(*roleId); 1354 if (priv.empty()) 1355 { 1356 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1357 return; 1358 } 1359 roleId = priv; 1360 1361 // Reading AllGroups property 1362 crow::connections::systemBus->async_method_call( 1363 [asyncResp, username, password{std::move(password)}, roleId, 1364 enabled](const boost::system::error_code ec, 1365 const std::variant<std::vector<std::string>>& allGroups) { 1366 if (ec) 1367 { 1368 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1369 messages::internalError(asyncResp->res); 1370 return; 1371 } 1372 1373 const std::vector<std::string>* allGroupsList = 1374 std::get_if<std::vector<std::string>>(&allGroups); 1375 1376 if (allGroupsList == nullptr || allGroupsList->empty()) 1377 { 1378 messages::internalError(asyncResp->res); 1379 return; 1380 } 1381 1382 crow::connections::systemBus->async_method_call( 1383 [asyncResp, username, password{std::move(password)}]( 1384 const boost::system::error_code ec) { 1385 if (ec) 1386 { 1387 messages::resourceAlreadyExists( 1388 asyncResp->res, 1389 "#ManagerAccount.v1_0_3.ManagerAccount", 1390 "UserName", username); 1391 return; 1392 } 1393 1394 if (!pamUpdatePassword(username, password)) 1395 { 1396 // At this point we have a user that's been created, 1397 // but the password set failed.Something is wrong, 1398 // so delete the user that we've already created 1399 crow::connections::systemBus->async_method_call( 1400 [asyncResp]( 1401 const boost::system::error_code ec) { 1402 if (ec) 1403 { 1404 messages::internalError(asyncResp->res); 1405 return; 1406 } 1407 1408 messages::invalidObject(asyncResp->res, 1409 "Password"); 1410 }, 1411 "xyz.openbmc_project.User.Manager", 1412 "/xyz/openbmc_project/user/" + username, 1413 "xyz.openbmc_project.Object.Delete", "Delete"); 1414 1415 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1416 return; 1417 } 1418 1419 messages::created(asyncResp->res); 1420 asyncResp->res.addHeader( 1421 "Location", 1422 "/redfish/v1/AccountService/Accounts/" + username); 1423 }, 1424 "xyz.openbmc_project.User.Manager", 1425 "/xyz/openbmc_project/user", 1426 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1427 *allGroupsList, *roleId, *enabled); 1428 }, 1429 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1430 "org.freedesktop.DBus.Properties", "Get", 1431 "xyz.openbmc_project.User.Manager", "AllGroups"); 1432 } 1433 }; 1434 1435 class ManagerAccount : public Node 1436 { 1437 public: 1438 ManagerAccount(CrowApp& app) : 1439 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1440 { 1441 entityPrivileges = { 1442 {boost::beast::http::verb::get, 1443 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1444 {boost::beast::http::verb::head, {{"Login"}}}, 1445 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1446 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1447 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1448 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1449 } 1450 1451 private: 1452 void doGet(crow::Response& res, const crow::Request& req, 1453 const std::vector<std::string>& params) override 1454 { 1455 1456 auto asyncResp = std::make_shared<AsyncResp>(res); 1457 1458 if (params.size() != 1) 1459 { 1460 messages::internalError(asyncResp->res); 1461 return; 1462 } 1463 1464 crow::connections::systemBus->async_method_call( 1465 [asyncResp, accountName{std::string(params[0])}]( 1466 const boost::system::error_code ec, 1467 const ManagedObjectType& users) { 1468 if (ec) 1469 { 1470 messages::internalError(asyncResp->res); 1471 return; 1472 } 1473 auto userIt = users.begin(); 1474 1475 for (; userIt != users.end(); userIt++) 1476 { 1477 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1478 { 1479 break; 1480 } 1481 } 1482 if (userIt == users.end()) 1483 { 1484 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1485 accountName); 1486 return; 1487 } 1488 1489 asyncResp->res.jsonValue = { 1490 {"@odata.context", 1491 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 1492 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 1493 {"Name", "User Account"}, 1494 {"Description", "User Account"}, 1495 {"Password", nullptr}}; 1496 1497 for (const auto& interface : userIt->second) 1498 { 1499 if (interface.first == 1500 "xyz.openbmc_project.User.Attributes") 1501 { 1502 for (const auto& property : interface.second) 1503 { 1504 if (property.first == "UserEnabled") 1505 { 1506 const bool* userEnabled = 1507 std::get_if<bool>(&property.second); 1508 if (userEnabled == nullptr) 1509 { 1510 BMCWEB_LOG_ERROR 1511 << "UserEnabled wasn't a bool"; 1512 messages::internalError(asyncResp->res); 1513 return; 1514 } 1515 asyncResp->res.jsonValue["Enabled"] = 1516 *userEnabled; 1517 } 1518 else if (property.first == 1519 "UserLockedForFailedAttempt") 1520 { 1521 const bool* userLocked = 1522 std::get_if<bool>(&property.second); 1523 if (userLocked == nullptr) 1524 { 1525 BMCWEB_LOG_ERROR << "UserLockedForF" 1526 "ailedAttempt " 1527 "wasn't a bool"; 1528 messages::internalError(asyncResp->res); 1529 return; 1530 } 1531 asyncResp->res.jsonValue["Locked"] = 1532 *userLocked; 1533 asyncResp->res.jsonValue 1534 ["Locked@Redfish.AllowableValues"] = { 1535 "false"}; 1536 } 1537 else if (property.first == "UserPrivilege") 1538 { 1539 const std::string* userPrivPtr = 1540 std::get_if<std::string>(&property.second); 1541 if (userPrivPtr == nullptr) 1542 { 1543 BMCWEB_LOG_ERROR 1544 << "UserPrivilege wasn't a " 1545 "string"; 1546 messages::internalError(asyncResp->res); 1547 return; 1548 } 1549 std::string role = 1550 getRoleIdFromPrivilege(*userPrivPtr); 1551 if (role.empty()) 1552 { 1553 BMCWEB_LOG_ERROR << "Invalid user role"; 1554 messages::internalError(asyncResp->res); 1555 return; 1556 } 1557 asyncResp->res.jsonValue["RoleId"] = role; 1558 1559 asyncResp->res.jsonValue["Links"]["Role"] = { 1560 {"@odata.id", "/redfish/v1/AccountService/" 1561 "Roles/" + 1562 role}}; 1563 } 1564 } 1565 } 1566 } 1567 1568 asyncResp->res.jsonValue["@odata.id"] = 1569 "/redfish/v1/AccountService/Accounts/" + accountName; 1570 asyncResp->res.jsonValue["Id"] = accountName; 1571 asyncResp->res.jsonValue["UserName"] = accountName; 1572 }, 1573 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1574 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1575 } 1576 1577 void doPatch(crow::Response& res, const crow::Request& req, 1578 const std::vector<std::string>& params) override 1579 { 1580 auto asyncResp = std::make_shared<AsyncResp>(res); 1581 if (params.size() != 1) 1582 { 1583 messages::internalError(asyncResp->res); 1584 return; 1585 } 1586 1587 std::optional<std::string> newUserName; 1588 std::optional<std::string> password; 1589 std::optional<bool> enabled; 1590 std::optional<std::string> roleId; 1591 std::optional<bool> locked; 1592 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1593 password, "RoleId", roleId, "Enabled", enabled, 1594 "Locked", locked)) 1595 { 1596 return; 1597 } 1598 1599 const std::string& username = params[0]; 1600 1601 if (!newUserName) 1602 { 1603 // If the username isn't being updated, we can update the 1604 // properties directly 1605 updateUserProperties(asyncResp, username, password, enabled, roleId, 1606 locked); 1607 return; 1608 } 1609 else 1610 { 1611 crow::connections::systemBus->async_method_call( 1612 [this, asyncResp, username, password(std::move(password)), 1613 roleId(std::move(roleId)), enabled(std::move(enabled)), 1614 newUser{*newUserName}, locked(std::move(locked))]( 1615 const boost::system::error_code ec) { 1616 if (ec) 1617 { 1618 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1619 messages::resourceNotFound( 1620 asyncResp->res, 1621 "#ManagerAccount.v1_0_3.ManagerAccount", username); 1622 return; 1623 } 1624 1625 updateUserProperties(asyncResp, newUser, password, enabled, 1626 roleId, locked); 1627 }, 1628 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1629 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1630 *newUserName); 1631 } 1632 } 1633 1634 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1635 const std::string& username, 1636 std::optional<std::string> password, 1637 std::optional<bool> enabled, 1638 std::optional<std::string> roleId, 1639 std::optional<bool> locked) 1640 { 1641 if (password) 1642 { 1643 if (!pamUpdatePassword(username, *password)) 1644 { 1645 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1646 messages::internalError(asyncResp->res); 1647 return; 1648 } 1649 } 1650 1651 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1652 dbus::utility::escapePathForDbus(dbusObjectPath); 1653 1654 dbus::utility::checkDbusPathExists( 1655 dbusObjectPath, 1656 [dbusObjectPath(std::move(dbusObjectPath)), username, 1657 password(std::move(password)), roleId(std::move(roleId)), 1658 enabled(std::move(enabled)), locked(std::move(locked)), 1659 asyncResp{std::move(asyncResp)}](int rc) { 1660 if (!rc) 1661 { 1662 messages::invalidObject(asyncResp->res, username.c_str()); 1663 return; 1664 } 1665 if (enabled) 1666 { 1667 crow::connections::systemBus->async_method_call( 1668 [asyncResp](const boost::system::error_code ec) { 1669 if (ec) 1670 { 1671 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1672 << ec; 1673 messages::internalError(asyncResp->res); 1674 return; 1675 } 1676 messages::success(asyncResp->res); 1677 return; 1678 }, 1679 "xyz.openbmc_project.User.Manager", 1680 dbusObjectPath.c_str(), 1681 "org.freedesktop.DBus.Properties", "Set", 1682 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1683 std::variant<bool>{*enabled}); 1684 } 1685 1686 if (roleId) 1687 { 1688 std::string priv = getPrivilegeFromRoleId(*roleId); 1689 if (priv.empty()) 1690 { 1691 messages::propertyValueNotInList(asyncResp->res, 1692 *roleId, "RoleId"); 1693 return; 1694 } 1695 1696 crow::connections::systemBus->async_method_call( 1697 [asyncResp](const boost::system::error_code ec) { 1698 if (ec) 1699 { 1700 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1701 << ec; 1702 messages::internalError(asyncResp->res); 1703 return; 1704 } 1705 messages::success(asyncResp->res); 1706 }, 1707 "xyz.openbmc_project.User.Manager", 1708 dbusObjectPath.c_str(), 1709 "org.freedesktop.DBus.Properties", "Set", 1710 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1711 std::variant<std::string>{priv}); 1712 } 1713 1714 if (locked) 1715 { 1716 // admin can unlock the account which is locked by 1717 // successive authentication failures but admin should 1718 // not be allowed to lock an account. 1719 if (*locked) 1720 { 1721 messages::propertyValueNotInList(asyncResp->res, "true", 1722 "Locked"); 1723 return; 1724 } 1725 1726 crow::connections::systemBus->async_method_call( 1727 [asyncResp](const boost::system::error_code ec) { 1728 if (ec) 1729 { 1730 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1731 << ec; 1732 messages::internalError(asyncResp->res); 1733 return; 1734 } 1735 messages::success(asyncResp->res); 1736 return; 1737 }, 1738 "xyz.openbmc_project.User.Manager", 1739 dbusObjectPath.c_str(), 1740 "org.freedesktop.DBus.Properties", "Set", 1741 "xyz.openbmc_project.User.Attributes", 1742 "UserLockedForFailedAttempt", 1743 sdbusplus::message::variant<bool>{*locked}); 1744 } 1745 }); 1746 } 1747 1748 void doDelete(crow::Response& res, const crow::Request& req, 1749 const std::vector<std::string>& params) override 1750 { 1751 auto asyncResp = std::make_shared<AsyncResp>(res); 1752 1753 if (params.size() != 1) 1754 { 1755 messages::internalError(asyncResp->res); 1756 return; 1757 } 1758 1759 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1760 1761 crow::connections::systemBus->async_method_call( 1762 [asyncResp, username{std::move(params[0])}]( 1763 const boost::system::error_code ec) { 1764 if (ec) 1765 { 1766 messages::resourceNotFound( 1767 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1768 username); 1769 return; 1770 } 1771 1772 messages::accountRemoved(asyncResp->res); 1773 }, 1774 "xyz.openbmc_project.User.Manager", userPath, 1775 "xyz.openbmc_project.Object.Delete", "Delete"); 1776 } 1777 }; 1778 1779 } // namespace redfish 1780