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* ldapRootObject = "/xyz/openbmc_project/user/ldap"; 31 constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config"; 32 constexpr const char* ldapConfigInterface = 33 "xyz.openbmc_project.User.Ldap.Config"; 34 constexpr const char* ldapCreateInterface = 35 "xyz.openbmc_project.User.Ldap.Create"; 36 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable"; 37 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; 38 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties"; 39 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper"; 40 constexpr const char* mapperObjectPath = "/xyz/openbmc_project/object_mapper"; 41 constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper"; 42 43 struct LDAPConfigData 44 { 45 std::string uri{}; 46 std::string bindDN{}; 47 std::string baseDN{}; 48 std::string searchScope{}; 49 std::string serverType{}; 50 bool serviceEnabled = false; 51 std::string userNameAttribute{}; 52 std::string groupAttribute{}; 53 }; 54 55 using ManagedObjectType = std::vector<std::pair< 56 sdbusplus::message::object_path, 57 boost::container::flat_map< 58 std::string, boost::container::flat_map< 59 std::string, std::variant<bool, std::string>>>>>; 60 using GetObjectType = 61 std::vector<std::pair<std::string, std::vector<std::string>>>; 62 63 inline std::string getPrivilegeFromRoleId(std::string_view role) 64 { 65 if (role == "priv-admin") 66 { 67 return "Administrator"; 68 } 69 else if (role == "priv-callback") 70 { 71 return "Callback"; 72 } 73 else if (role == "priv-user") 74 { 75 return "User"; 76 } 77 else if (role == "priv-operator") 78 { 79 return "Operator"; 80 } 81 return ""; 82 } 83 inline std::string getRoleIdFromPrivilege(std::string_view role) 84 { 85 if (role == "Administrator") 86 { 87 return "priv-admin"; 88 } 89 else if (role == "Callback") 90 { 91 return "priv-callback"; 92 } 93 else if (role == "User") 94 { 95 return "priv-user"; 96 } 97 else if (role == "Operator") 98 { 99 return "priv-operator"; 100 } 101 return ""; 102 } 103 104 void parseLDAPConfigData(nlohmann::json& json_response, 105 const LDAPConfigData& confData) 106 { 107 std::string service = "LDAPService"; 108 json_response["LDAP"] = { 109 {"AccountProviderType", service}, 110 {"ServiceEnabled", confData.serviceEnabled}, 111 {"ServiceAddresses", nlohmann::json::array({confData.uri})}, 112 {"Authentication", 113 {{"AuthenticationType", "UsernameAndPassword"}, 114 {"Username", confData.bindDN}, 115 {"Password", nullptr}}}, 116 {"LDAPService", 117 {{"SearchSettings", 118 {{"BaseDistinguishedNames", 119 nlohmann::json::array({confData.baseDN})}, 120 {"UsernameAttribute", confData.userNameAttribute}, 121 {"GroupsAttribute", confData.groupAttribute}}}}}, 122 }; 123 } 124 125 /** 126 * Function that retrieves all properties for LDAP config object 127 * into JSON 128 */ 129 template <typename CallbackFunc> 130 inline void getLDAPConfigData(const std::string& ldapType, 131 CallbackFunc&& callback) 132 { 133 auto getConfig = [callback, 134 ldapType](const boost::system::error_code error_code, 135 const ManagedObjectType& ldapObjects) { 136 LDAPConfigData confData{}; 137 if (error_code) 138 { 139 callback(false, confData); 140 BMCWEB_LOG_ERROR << "D-Bus responses error: " << error_code; 141 return; 142 } 143 std::string ldapConfigObjectStr = std::string(ldapConfigObject); 144 std::string ldapEnableInterfaceStr = std::string(ldapEnableInterface); 145 std::string ldapConfigInterfaceStr = std::string(ldapConfigInterface); 146 for (const auto& object : ldapObjects) 147 { 148 if (object.first == ldapConfigObjectStr) 149 { 150 for (const auto& interface : object.second) 151 { 152 if (interface.first == ldapEnableInterfaceStr) 153 { 154 // rest of the properties are string. 155 for (const auto& property : interface.second) 156 { 157 if (property.first == "Enabled") 158 { 159 const bool* value = 160 std::get_if<bool>(&property.second); 161 if (value == nullptr) 162 { 163 continue; 164 } 165 confData.serviceEnabled = *value; 166 break; 167 } 168 } 169 } 170 else if (interface.first == ldapConfigInterfaceStr) 171 { 172 173 for (const auto& property : interface.second) 174 { 175 const std::string* value = 176 std::get_if<std::string>(&property.second); 177 if (value == nullptr) 178 { 179 continue; 180 } 181 if (property.first == "LDAPServerURI") 182 { 183 confData.uri = *value; 184 } 185 else if (property.first == "LDAPBindDN") 186 { 187 confData.bindDN = *value; 188 } 189 else if (property.first == "LDAPBaseDN") 190 { 191 confData.baseDN = *value; 192 } 193 else if (property.first == "LDAPSearchScope") 194 { 195 confData.searchScope = *value; 196 } 197 else if (property.first == "LDAPType") 198 { 199 confData.serverType = *value; 200 } 201 else if (property.first == "GroupNameAttribute") 202 { 203 confData.groupAttribute = *value; 204 } 205 else if (property.first == "UserNameAttribute") 206 { 207 confData.userNameAttribute = *value; 208 } 209 } 210 } 211 } 212 213 callback(true, confData); 214 break; 215 } 216 } 217 }; 218 auto getServiceName = [callback, getConfig(std::move(getConfig))]( 219 const boost::system::error_code ec, 220 const GetObjectType& resp) { 221 LDAPConfigData confData{}; 222 if (ec || resp.empty()) 223 { 224 BMCWEB_LOG_ERROR 225 << "DBUS response error during getting of service name: " << ec; 226 callback(false, confData); 227 return; 228 } 229 std::string service = resp.begin()->first; 230 crow::connections::systemBus->async_method_call( 231 std::move(getConfig), service, ldapRootObject, dbusObjManagerIntf, 232 "GetManagedObjects"); 233 }; 234 235 const std::array<std::string, 2> interfaces = {ldapEnableInterface, 236 ldapConfigInterface}; 237 238 crow::connections::systemBus->async_method_call( 239 std::move(getServiceName), mapperBusName, mapperObjectPath, mapperIntf, 240 "GetObject", ldapConfigObject, interfaces); 241 } 242 243 class AccountService : public Node 244 { 245 public: 246 AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/") 247 { 248 entityPrivileges = { 249 {boost::beast::http::verb::get, 250 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 251 {boost::beast::http::verb::head, {{"Login"}}}, 252 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 253 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 254 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 255 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 256 } 257 258 private: 259 /** 260 * @brief parses the authentication section under the LDAP 261 * @param input JSON data 262 * @param asyncResp pointer to the JSON response 263 * @param userName userName to be filled from the given JSON. 264 * @param password password to be filled from the given JSON. 265 */ 266 void 267 parseLDAPAuthenticationJson(nlohmann::json input, 268 const std::shared_ptr<AsyncResp>& asyncResp, 269 std::optional<std::string>& username, 270 std::optional<std::string>& password) 271 { 272 std::optional<std::string> authType; 273 274 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 275 authType, "Username", username, "Password", 276 password)) 277 { 278 return; 279 } 280 if (!authType) 281 { 282 return; 283 } 284 if (*authType != "UsernameAndPassword") 285 { 286 messages::propertyValueNotInList(asyncResp->res, *authType, 287 "AuthenticationType"); 288 return; 289 } 290 } 291 /** 292 * @brief parses the LDAPService section under the LDAP 293 * @param input JSON data 294 * @param asyncResp pointer to the JSON response 295 * @param baseDNList baseDN to be filled from the given JSON. 296 * @param userNameAttribute userName to be filled from the given JSON. 297 * @param groupaAttribute password to be filled from the given JSON. 298 */ 299 300 void parseLDAPServiceJson( 301 nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp, 302 std::optional<std::vector<std::string>>& baseDNList, 303 std::optional<std::string>& userNameAttribute, 304 std::optional<std::string>& groupsAttribute) 305 { 306 std::optional<nlohmann::json> searchSettings; 307 308 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 309 searchSettings)) 310 { 311 return; 312 } 313 if (!searchSettings) 314 { 315 return; 316 } 317 if (!json_util::readJson(*searchSettings, asyncResp->res, 318 "BaseDistinguishedNames", baseDNList, 319 "UsernameAttribute", userNameAttribute, 320 "GroupsAttribute", groupsAttribute)) 321 { 322 return; 323 } 324 } 325 /** 326 * @brief updates the LDAP server address and updates the 327 json response with the new value. 328 * @param serviceAddressList address to be updated. 329 * @param asyncResp pointer to the JSON response 330 * @param ldapServerElementName Type of LDAP 331 server(openLDAP/ActiveDirectory) 332 */ 333 334 void handleServiceAddressPatch( 335 const std::vector<std::string>& serviceAddressList, 336 const std::shared_ptr<AsyncResp>& asyncResp, 337 const std::string& ldapServerElementName, 338 const std::string& ldapConfigObject) 339 { 340 crow::connections::systemBus->async_method_call( 341 [asyncResp, ldapServerElementName, 342 serviceAddressList](const boost::system::error_code ec) { 343 if (ec) 344 { 345 BMCWEB_LOG_DEBUG 346 << "Error Occured in updating the service address"; 347 messages::internalError(asyncResp->res); 348 return; 349 } 350 std::vector<std::string> modifiedserviceAddressList = { 351 serviceAddressList.front()}; 352 asyncResp->res 353 .jsonValue[ldapServerElementName]["ServiceAddresses"] = 354 modifiedserviceAddressList; 355 if ((serviceAddressList).size() > 1) 356 { 357 messages::propertyValueModified(asyncResp->res, 358 "ServiceAddresses", 359 serviceAddressList.front()); 360 } 361 BMCWEB_LOG_DEBUG << "Updated the service address"; 362 }, 363 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 364 ldapConfigInterface, "LDAPServerURI", 365 std::variant<std::string>(serviceAddressList.front())); 366 } 367 /** 368 * @brief updates the LDAP Bind DN and updates the 369 json response with the new value. 370 * @param username name of the user which needs to be updated. 371 * @param asyncResp pointer to the JSON response 372 * @param ldapServerElementName Type of LDAP 373 server(openLDAP/ActiveDirectory) 374 */ 375 376 void handleUserNamePatch(const std::string& username, 377 const std::shared_ptr<AsyncResp>& asyncResp, 378 const std::string& ldapServerElementName, 379 const std::string& ldapConfigObject) 380 { 381 crow::connections::systemBus->async_method_call( 382 [asyncResp, username, 383 ldapServerElementName](const boost::system::error_code ec) { 384 if (ec) 385 { 386 BMCWEB_LOG_DEBUG 387 << "Error occured in updating the username"; 388 messages::internalError(asyncResp->res); 389 return; 390 } 391 asyncResp->res.jsonValue[ldapServerElementName] 392 ["Authentication"]["Username"] = 393 username; 394 BMCWEB_LOG_DEBUG << "Updated the username"; 395 }, 396 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 397 ldapConfigInterface, "LDAPBindDN", 398 std::variant<std::string>(username)); 399 } 400 401 /** 402 * @brief updates the LDAP password 403 * @param password : ldap password which needs to be updated. 404 * @param asyncResp pointer to the JSON response 405 * @param ldapServerElementName Type of LDAP 406 * server(openLDAP/ActiveDirectory) 407 */ 408 409 void handlePasswordPatch(const std::string& password, 410 const std::shared_ptr<AsyncResp>& asyncResp, 411 const std::string& ldapServerElementName, 412 const std::string& ldapConfigObject) 413 { 414 crow::connections::systemBus->async_method_call( 415 [asyncResp, password, 416 ldapServerElementName](const boost::system::error_code ec) { 417 if (ec) 418 { 419 BMCWEB_LOG_DEBUG 420 << "Error occured in updating the password"; 421 messages::internalError(asyncResp->res); 422 return; 423 } 424 asyncResp->res.jsonValue[ldapServerElementName] 425 ["Authentication"]["Password"] = ""; 426 BMCWEB_LOG_DEBUG << "Updated the password"; 427 }, 428 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 429 ldapConfigInterface, "LDAPBindDNPassword", 430 std::variant<std::string>(password)); 431 } 432 433 /** 434 * @brief updates the LDAP BaseDN and updates the 435 json response with the new value. 436 * @param baseDNList baseDN list which needs to be updated. 437 * @param asyncResp pointer to the JSON response 438 * @param ldapServerElementName Type of LDAP 439 server(openLDAP/ActiveDirectory) 440 */ 441 442 void handleBaseDNPatch(const std::vector<std::string>& baseDNList, 443 const std::shared_ptr<AsyncResp>& asyncResp, 444 const std::string& ldapServerElementName, 445 const std::string& ldapConfigObject) 446 { 447 crow::connections::systemBus->async_method_call( 448 [asyncResp, baseDNList, 449 ldapServerElementName](const boost::system::error_code ec) { 450 if (ec) 451 { 452 BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN"; 453 messages::internalError(asyncResp->res); 454 return; 455 } 456 auto& serverTypeJson = 457 asyncResp->res.jsonValue[ldapServerElementName]; 458 auto& searchSettingsJson = 459 serverTypeJson["LDAPService"]["SearchSettings"]; 460 std::vector<std::string> modifiedBaseDNList = { 461 baseDNList.front()}; 462 searchSettingsJson["BaseDistinguishedNames"] = 463 modifiedBaseDNList; 464 if (baseDNList.size() > 1) 465 { 466 messages::propertyValueModified(asyncResp->res, 467 "BaseDistinguishedNames", 468 baseDNList.front()); 469 } 470 BMCWEB_LOG_DEBUG << "Updated the base DN"; 471 }, 472 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 473 ldapConfigInterface, "LDAPBaseDN", 474 std::variant<std::string>(baseDNList.front())); 475 } 476 /** 477 * @brief updates the LDAP user name attribute and updates the 478 json response with the new value. 479 * @param userNameAttribute attribute to be updated. 480 * @param asyncResp pointer to the JSON response 481 * @param ldapServerElementName Type of LDAP 482 server(openLDAP/ActiveDirectory) 483 */ 484 485 void handleUserNameAttrPatch(const std::string& userNameAttribute, 486 const std::shared_ptr<AsyncResp>& asyncResp, 487 const std::string& ldapServerElementName, 488 const std::string& ldapConfigObject) 489 { 490 crow::connections::systemBus->async_method_call( 491 [asyncResp, userNameAttribute, 492 ldapServerElementName](const boost::system::error_code ec) { 493 if (ec) 494 { 495 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 496 "username attribute"; 497 messages::internalError(asyncResp->res); 498 return; 499 } 500 auto& serverTypeJson = 501 asyncResp->res.jsonValue[ldapServerElementName]; 502 auto& searchSettingsJson = 503 serverTypeJson["LDAPService"]["SearchSettings"]; 504 searchSettingsJson["UsernameAttribute"] = userNameAttribute; 505 BMCWEB_LOG_DEBUG << "Updated the user name attr."; 506 }, 507 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 508 ldapConfigInterface, "UserNameAttribute", 509 std::variant<std::string>(userNameAttribute)); 510 } 511 /** 512 * @brief updates the LDAP group attribute and updates the 513 json response with the new value. 514 * @param groupsAttribute attribute to be updated. 515 * @param asyncResp pointer to the JSON response 516 * @param ldapServerElementName Type of LDAP 517 server(openLDAP/ActiveDirectory) 518 */ 519 520 void handleGroupNameAttrPatch(const std::string& groupsAttribute, 521 const std::shared_ptr<AsyncResp>& asyncResp, 522 const std::string& ldapServerElementName, 523 const std::string& ldapConfigObject) 524 { 525 crow::connections::systemBus->async_method_call( 526 [asyncResp, groupsAttribute, 527 ldapServerElementName](const boost::system::error_code ec) { 528 if (ec) 529 { 530 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 531 "groupname attribute"; 532 messages::internalError(asyncResp->res); 533 return; 534 } 535 auto& serverTypeJson = 536 asyncResp->res.jsonValue[ldapServerElementName]; 537 auto& searchSettingsJson = 538 serverTypeJson["LDAPService"]["SearchSettings"]; 539 searchSettingsJson["GroupsAttribute"] = groupsAttribute; 540 BMCWEB_LOG_DEBUG << "Updated the groupname attr"; 541 }, 542 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 543 ldapConfigInterface, "GroupNameAttribute", 544 std::variant<std::string>(groupsAttribute)); 545 } 546 /** 547 * @brief updates the LDAP service enable and updates the 548 json response with the new value. 549 * @param input JSON data. 550 * @param asyncResp pointer to the JSON response 551 * @param ldapServerElementName Type of LDAP 552 server(openLDAP/ActiveDirectory) 553 */ 554 555 void handleServiceEnablePatch(bool serviceEnabled, 556 const std::shared_ptr<AsyncResp>& asyncResp, 557 const std::string& ldapServerElementName, 558 const std::string& ldapConfigObject) 559 { 560 crow::connections::systemBus->async_method_call( 561 [asyncResp, serviceEnabled, 562 ldapServerElementName](const boost::system::error_code ec) { 563 if (ec) 564 { 565 BMCWEB_LOG_DEBUG 566 << "Error Occured in Updating the service enable"; 567 messages::internalError(asyncResp->res); 568 return; 569 } 570 asyncResp->res 571 .jsonValue[ldapServerElementName]["ServiceEnabled"] = 572 serviceEnabled; 573 BMCWEB_LOG_DEBUG << "Updated Service enable = " 574 << serviceEnabled; 575 }, 576 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 577 ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled)); 578 } 579 580 /** 581 * @brief Get the required values from the given JSON, validates the 582 * value and create the LDAP config object. 583 * @param input JSON data 584 * @param asyncResp pointer to the JSON response 585 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 586 */ 587 588 void handleLDAPPatch(nlohmann::json& input, 589 const std::shared_ptr<AsyncResp>& asyncResp, 590 const crow::Request& req, 591 const std::vector<std::string>& params, 592 const std::string& serverType) 593 { 594 std::optional<nlohmann::json> authentication; 595 std::optional<nlohmann::json> ldapService; 596 std::optional<std::string> accountProviderType; 597 std::optional<std::vector<std::string>> serviceAddressList; 598 std::optional<bool> serviceEnabled; 599 std::optional<std::vector<std::string>> baseDNList; 600 std::optional<std::string> userNameAttribute; 601 std::optional<std::string> groupsAttribute; 602 std::optional<std::string> userName; 603 std::optional<std::string> password; 604 605 if (!json_util::readJson(input, asyncResp->res, "Authentication", 606 authentication, "LDAPService", ldapService, 607 "ServiceAddresses", serviceAddressList, 608 "AccountProviderType", accountProviderType, 609 "ServiceEnabled", serviceEnabled)) 610 { 611 return; 612 } 613 614 if (authentication) 615 { 616 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 617 password); 618 } 619 if (ldapService) 620 { 621 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 622 userNameAttribute, groupsAttribute); 623 } 624 if (accountProviderType) 625 { 626 messages::propertyNotWritable(asyncResp->res, 627 "AccountProviderType"); 628 } 629 if (serviceAddressList) 630 { 631 if ((*serviceAddressList).size() == 0) 632 { 633 messages::propertyValueNotInList(asyncResp->res, "[]", 634 "ServiceAddress"); 635 return; 636 } 637 } 638 if (baseDNList) 639 { 640 if ((*baseDNList).size() == 0) 641 { 642 messages::propertyValueNotInList(asyncResp->res, "[]", 643 "BaseDistinguishedNames"); 644 return; 645 } 646 } 647 648 // nothing to update, then return 649 if (!userName && !password && !serviceAddressList && !baseDNList && 650 !userNameAttribute && !groupsAttribute && !serviceEnabled) 651 { 652 return; 653 } 654 655 // Get the existing resource first then keep modifying 656 // whenever any property gets updated. 657 getLDAPConfigData( 658 serverType, 659 [this, asyncResp, userName, password, baseDNList, userNameAttribute, 660 groupsAttribute, accountProviderType, serviceAddressList, 661 serviceEnabled, 662 serverType](bool success, LDAPConfigData confData) { 663 if (!success) 664 { 665 messages::internalError(asyncResp->res); 666 return; 667 } 668 parseLDAPConfigData(asyncResp->res.jsonValue, confData); 669 if (confData.serviceEnabled) 670 { 671 // Disable the service first and update the rest of 672 // the properties. 673 handleServiceEnablePatch(false, asyncResp, serverType, 674 ldapConfigObject); 675 } 676 677 if (serviceAddressList) 678 { 679 handleServiceAddressPatch(*serviceAddressList, asyncResp, 680 serverType, ldapConfigObject); 681 } 682 if (userName) 683 { 684 handleUserNamePatch(*userName, asyncResp, serverType, 685 ldapConfigObject); 686 } 687 if (password) 688 { 689 handlePasswordPatch(*password, asyncResp, serverType, 690 ldapConfigObject); 691 } 692 693 if (baseDNList) 694 { 695 handleBaseDNPatch(*baseDNList, asyncResp, serverType, 696 ldapConfigObject); 697 } 698 if (userNameAttribute) 699 { 700 handleUserNameAttrPatch(*userNameAttribute, asyncResp, 701 serverType, ldapConfigObject); 702 } 703 if (groupsAttribute) 704 { 705 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, 706 serverType, ldapConfigObject); 707 } 708 if (serviceEnabled) 709 { 710 // if user has given the value as true then enable 711 // the service. if user has given false then no-op 712 // as service is already stopped. 713 if (*serviceEnabled) 714 { 715 handleServiceEnablePatch(*serviceEnabled, asyncResp, 716 serverType, ldapConfigObject); 717 } 718 } 719 else 720 { 721 // if user has not given the service enabled value 722 // then revert it to the same state as it was 723 // before. 724 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 725 serverType, ldapConfigObject); 726 } 727 }); 728 } 729 730 void doGet(crow::Response& res, const crow::Request& req, 731 const std::vector<std::string>& params) override 732 { 733 auto asyncResp = std::make_shared<AsyncResp>(res); 734 res.jsonValue = { 735 {"@odata.context", "/redfish/v1/" 736 "$metadata#AccountService.AccountService"}, 737 {"@odata.id", "/redfish/v1/AccountService"}, 738 {"@odata.type", "#AccountService." 739 "v1_3_1.AccountService"}, 740 {"Id", "AccountService"}, 741 {"Name", "Account Service"}, 742 {"Description", "Account Service"}, 743 {"ServiceEnabled", true}, 744 {"MaxPasswordLength", 20}, 745 {"Accounts", 746 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 747 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}}; 748 749 crow::connections::systemBus->async_method_call( 750 [asyncResp]( 751 const boost::system::error_code ec, 752 const std::vector<std::pair< 753 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 754 propertiesList) { 755 if (ec) 756 { 757 messages::internalError(asyncResp->res); 758 return; 759 } 760 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 761 << "properties for AccountService"; 762 for (const std::pair<std::string, 763 std::variant<uint32_t, uint16_t, uint8_t>>& 764 property : propertiesList) 765 { 766 if (property.first == "MinPasswordLength") 767 { 768 const uint8_t* value = 769 std::get_if<uint8_t>(&property.second); 770 if (value != nullptr) 771 { 772 asyncResp->res.jsonValue["MinPasswordLength"] = 773 *value; 774 } 775 } 776 if (property.first == "AccountUnlockTimeout") 777 { 778 const uint32_t* value = 779 std::get_if<uint32_t>(&property.second); 780 if (value != nullptr) 781 { 782 asyncResp->res.jsonValue["AccountLockoutDuration"] = 783 *value; 784 } 785 } 786 if (property.first == "MaxLoginAttemptBeforeLockout") 787 { 788 const uint16_t* value = 789 std::get_if<uint16_t>(&property.second); 790 if (value != nullptr) 791 { 792 asyncResp->res 793 .jsonValue["AccountLockoutThreshold"] = *value; 794 } 795 } 796 } 797 }, 798 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 799 "org.freedesktop.DBus.Properties", "GetAll", 800 "xyz.openbmc_project.User.AccountPolicy"); 801 802 std::string ldapType = "LDAP"; 803 getLDAPConfigData( 804 ldapType, 805 [asyncResp, ldapType](bool success, LDAPConfigData& confData) { 806 parseLDAPConfigData(asyncResp->res.jsonValue, confData); 807 }); 808 } 809 810 void doPatch(crow::Response& res, const crow::Request& req, 811 const std::vector<std::string>& params) override 812 { 813 auto asyncResp = std::make_shared<AsyncResp>(res); 814 815 std::optional<uint32_t> unlockTimeout; 816 std::optional<uint16_t> lockoutThreshold; 817 std::optional<uint16_t> minPasswordLength; 818 std::optional<uint16_t> maxPasswordLength; 819 std::optional<nlohmann::json> ldapObject; 820 821 if (!json_util::readJson(req, res, "AccountLockoutDuration", 822 unlockTimeout, "AccountLockoutThreshold", 823 lockoutThreshold, "MaxPasswordLength", 824 maxPasswordLength, "MinPasswordLength", 825 minPasswordLength)) 826 { 827 return; 828 } 829 830 if (minPasswordLength) 831 { 832 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 833 } 834 835 if (maxPasswordLength) 836 { 837 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 838 } 839 840 if (ldapObject) 841 { 842 handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP"); 843 } 844 845 if (unlockTimeout) 846 { 847 crow::connections::systemBus->async_method_call( 848 [asyncResp](const boost::system::error_code ec) { 849 if (ec) 850 { 851 messages::internalError(asyncResp->res); 852 return; 853 } 854 messages::success(asyncResp->res); 855 }, 856 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 857 "org.freedesktop.DBus.Properties", "Set", 858 "xyz.openbmc_project.User.AccountPolicy", 859 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 860 } 861 if (lockoutThreshold) 862 { 863 crow::connections::systemBus->async_method_call( 864 [asyncResp](const boost::system::error_code ec) { 865 if (ec) 866 { 867 messages::internalError(asyncResp->res); 868 return; 869 } 870 messages::success(asyncResp->res); 871 }, 872 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 873 "org.freedesktop.DBus.Properties", "Set", 874 "xyz.openbmc_project.User.AccountPolicy", 875 "MaxLoginAttemptBeforeLockout", 876 std::variant<uint16_t>(*lockoutThreshold)); 877 } 878 } 879 }; 880 881 class AccountsCollection : public Node 882 { 883 public: 884 AccountsCollection(CrowApp& app) : 885 Node(app, "/redfish/v1/AccountService/Accounts/") 886 { 887 entityPrivileges = { 888 {boost::beast::http::verb::get, 889 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 890 {boost::beast::http::verb::head, {{"Login"}}}, 891 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 892 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 893 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 894 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 895 } 896 897 private: 898 void doGet(crow::Response& res, const crow::Request& req, 899 const std::vector<std::string>& params) override 900 { 901 auto asyncResp = std::make_shared<AsyncResp>(res); 902 res.jsonValue = {{"@odata.context", 903 "/redfish/v1/" 904 "$metadata#ManagerAccountCollection." 905 "ManagerAccountCollection"}, 906 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 907 {"@odata.type", "#ManagerAccountCollection." 908 "ManagerAccountCollection"}, 909 {"Name", "Accounts Collection"}, 910 {"Description", "BMC User Accounts"}}; 911 912 crow::connections::systemBus->async_method_call( 913 [asyncResp](const boost::system::error_code ec, 914 const ManagedObjectType& users) { 915 if (ec) 916 { 917 messages::internalError(asyncResp->res); 918 return; 919 } 920 921 nlohmann::json& memberArray = 922 asyncResp->res.jsonValue["Members"]; 923 memberArray = nlohmann::json::array(); 924 925 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 926 for (auto& user : users) 927 { 928 const std::string& path = 929 static_cast<const std::string&>(user.first); 930 std::size_t lastIndex = path.rfind("/"); 931 if (lastIndex == std::string::npos) 932 { 933 lastIndex = 0; 934 } 935 else 936 { 937 lastIndex += 1; 938 } 939 memberArray.push_back( 940 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 941 path.substr(lastIndex)}}); 942 } 943 }, 944 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 945 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 946 } 947 void doPost(crow::Response& res, const crow::Request& req, 948 const std::vector<std::string>& params) override 949 { 950 auto asyncResp = std::make_shared<AsyncResp>(res); 951 952 std::string username; 953 std::string password; 954 std::optional<std::string> roleId("User"); 955 std::optional<bool> enabled = true; 956 if (!json_util::readJson(req, res, "UserName", username, "Password", 957 password, "RoleId", roleId, "Enabled", 958 enabled)) 959 { 960 return; 961 } 962 963 std::string priv = getRoleIdFromPrivilege(*roleId); 964 if (priv.empty()) 965 { 966 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 967 return; 968 } 969 roleId = priv; 970 971 crow::connections::systemBus->async_method_call( 972 [asyncResp, username, password{std::move(password)}]( 973 const boost::system::error_code ec) { 974 if (ec) 975 { 976 messages::resourceAlreadyExists( 977 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 978 "UserName", username); 979 return; 980 } 981 982 if (!pamUpdatePassword(username, password)) 983 { 984 // At this point we have a user that's been created, but the 985 // password set failed. Something is wrong, so delete the 986 // user that we've already created 987 crow::connections::systemBus->async_method_call( 988 [asyncResp](const boost::system::error_code ec) { 989 if (ec) 990 { 991 messages::internalError(asyncResp->res); 992 return; 993 } 994 995 messages::invalidObject(asyncResp->res, "Password"); 996 }, 997 "xyz.openbmc_project.User.Manager", 998 "/xyz/openbmc_project/user/" + username, 999 "xyz.openbmc_project.Object.Delete", "Delete"); 1000 1001 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1002 return; 1003 } 1004 1005 messages::created(asyncResp->res); 1006 asyncResp->res.addHeader( 1007 "Location", 1008 "/redfish/v1/AccountService/Accounts/" + username); 1009 }, 1010 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1011 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1012 std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"}, 1013 *roleId, *enabled); 1014 } 1015 }; 1016 1017 class ManagerAccount : public Node 1018 { 1019 public: 1020 ManagerAccount(CrowApp& app) : 1021 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1022 { 1023 entityPrivileges = { 1024 {boost::beast::http::verb::get, 1025 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1026 {boost::beast::http::verb::head, {{"Login"}}}, 1027 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1028 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1029 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1030 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1031 } 1032 1033 private: 1034 void doGet(crow::Response& res, const crow::Request& req, 1035 const std::vector<std::string>& params) override 1036 { 1037 res.jsonValue = { 1038 {"@odata.context", 1039 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 1040 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 1041 {"Name", "User Account"}, 1042 {"Description", "User Account"}, 1043 {"Password", nullptr}, 1044 {"RoleId", "Administrator"}}; 1045 1046 auto asyncResp = std::make_shared<AsyncResp>(res); 1047 1048 if (params.size() != 1) 1049 { 1050 messages::internalError(asyncResp->res); 1051 return; 1052 } 1053 1054 crow::connections::systemBus->async_method_call( 1055 [asyncResp, accountName{std::string(params[0])}]( 1056 const boost::system::error_code ec, 1057 const ManagedObjectType& users) { 1058 if (ec) 1059 { 1060 messages::internalError(asyncResp->res); 1061 return; 1062 } 1063 auto userIt = users.begin(); 1064 1065 for (; userIt != users.end(); userIt++) 1066 { 1067 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1068 { 1069 break; 1070 } 1071 } 1072 if (userIt == users.end()) 1073 { 1074 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1075 accountName); 1076 return; 1077 } 1078 for (const auto& interface : userIt->second) 1079 { 1080 if (interface.first == 1081 "xyz.openbmc_project.User.Attributes") 1082 { 1083 for (const auto& property : interface.second) 1084 { 1085 if (property.first == "UserEnabled") 1086 { 1087 const bool* userEnabled = 1088 std::get_if<bool>(&property.second); 1089 if (userEnabled == nullptr) 1090 { 1091 BMCWEB_LOG_ERROR 1092 << "UserEnabled wasn't a bool"; 1093 messages::internalError(asyncResp->res); 1094 return; 1095 } 1096 asyncResp->res.jsonValue["Enabled"] = 1097 *userEnabled; 1098 } 1099 else if (property.first == 1100 "UserLockedForFailedAttempt") 1101 { 1102 const bool* userLocked = 1103 std::get_if<bool>(&property.second); 1104 if (userLocked == nullptr) 1105 { 1106 BMCWEB_LOG_ERROR << "UserLockedForF" 1107 "ailedAttempt " 1108 "wasn't a bool"; 1109 messages::internalError(asyncResp->res); 1110 return; 1111 } 1112 asyncResp->res.jsonValue["Locked"] = 1113 *userLocked; 1114 asyncResp->res.jsonValue 1115 ["Locked@Redfish.AllowableValues"] = { 1116 "false"}; 1117 } 1118 else if (property.first == "UserPrivilege") 1119 { 1120 const std::string* userRolePtr = 1121 std::get_if<std::string>(&property.second); 1122 if (userRolePtr == nullptr) 1123 { 1124 BMCWEB_LOG_ERROR 1125 << "UserPrivilege wasn't a " 1126 "string"; 1127 messages::internalError(asyncResp->res); 1128 return; 1129 } 1130 std::string priv = 1131 getPrivilegeFromRoleId(*userRolePtr); 1132 if (priv.empty()) 1133 { 1134 BMCWEB_LOG_ERROR << "Invalid user role"; 1135 messages::internalError(asyncResp->res); 1136 return; 1137 } 1138 asyncResp->res.jsonValue["RoleId"] = priv; 1139 1140 asyncResp->res.jsonValue["Links"]["Role"] = { 1141 {"@odata.id", "/redfish/v1/AccountService/" 1142 "Roles/" + 1143 priv}}; 1144 } 1145 } 1146 } 1147 } 1148 1149 asyncResp->res.jsonValue["@odata.id"] = 1150 "/redfish/v1/AccountService/Accounts/" + accountName; 1151 asyncResp->res.jsonValue["Id"] = accountName; 1152 asyncResp->res.jsonValue["UserName"] = accountName; 1153 }, 1154 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1155 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1156 } 1157 1158 void doPatch(crow::Response& res, const crow::Request& req, 1159 const std::vector<std::string>& params) override 1160 { 1161 auto asyncResp = std::make_shared<AsyncResp>(res); 1162 if (params.size() != 1) 1163 { 1164 messages::internalError(asyncResp->res); 1165 return; 1166 } 1167 1168 std::optional<std::string> newUserName; 1169 std::optional<std::string> password; 1170 std::optional<bool> enabled; 1171 std::optional<std::string> roleId; 1172 std::optional<bool> locked; 1173 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1174 password, "RoleId", roleId, "Enabled", enabled, 1175 "Locked", locked)) 1176 { 1177 return; 1178 } 1179 1180 const std::string& username = params[0]; 1181 1182 if (!newUserName) 1183 { 1184 // If the username isn't being updated, we can update the properties 1185 // directly 1186 updateUserProperties(asyncResp, username, password, enabled, roleId, 1187 locked); 1188 return; 1189 } 1190 else 1191 { 1192 crow::connections::systemBus->async_method_call( 1193 [this, asyncResp, username, password(std::move(password)), 1194 roleId(std::move(roleId)), enabled(std::move(enabled)), 1195 newUser{std::string(*newUserName)}, locked(std::move(locked))]( 1196 const boost::system::error_code ec) { 1197 if (ec) 1198 { 1199 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1200 messages::resourceNotFound( 1201 asyncResp->res, 1202 "#ManagerAccount.v1_0_3.ManagerAccount", username); 1203 return; 1204 } 1205 1206 updateUserProperties(asyncResp, newUser, password, enabled, 1207 roleId, locked); 1208 }, 1209 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1210 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1211 *newUserName); 1212 } 1213 } 1214 1215 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1216 const std::string& username, 1217 std::optional<std::string> password, 1218 std::optional<bool> enabled, 1219 std::optional<std::string> roleId, 1220 std::optional<bool> locked) 1221 { 1222 if (password) 1223 { 1224 if (!pamUpdatePassword(username, *password)) 1225 { 1226 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1227 messages::internalError(asyncResp->res); 1228 return; 1229 } 1230 } 1231 1232 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1233 dbus::utility::escapePathForDbus(dbusObjectPath); 1234 1235 dbus::utility::checkDbusPathExists( 1236 dbusObjectPath, 1237 [dbusObjectPath(std::move(dbusObjectPath)), username, 1238 password(std::move(password)), roleId(std::move(roleId)), 1239 enabled(std::move(enabled)), locked(std::move(locked)), 1240 asyncResp{std::move(asyncResp)}](int rc) { 1241 if (!rc) 1242 { 1243 messages::invalidObject(asyncResp->res, username.c_str()); 1244 return; 1245 } 1246 if (enabled) 1247 { 1248 crow::connections::systemBus->async_method_call( 1249 [asyncResp](const boost::system::error_code ec) { 1250 if (ec) 1251 { 1252 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1253 << ec; 1254 messages::internalError(asyncResp->res); 1255 return; 1256 } 1257 messages::success(asyncResp->res); 1258 return; 1259 }, 1260 "xyz.openbmc_project.User.Manager", 1261 dbusObjectPath.c_str(), 1262 "org.freedesktop.DBus.Properties", "Set", 1263 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1264 std::variant<bool>{*enabled}); 1265 } 1266 1267 if (roleId) 1268 { 1269 std::string priv = getRoleIdFromPrivilege(*roleId); 1270 if (priv.empty()) 1271 { 1272 messages::propertyValueNotInList(asyncResp->res, 1273 *roleId, "RoleId"); 1274 return; 1275 } 1276 1277 crow::connections::systemBus->async_method_call( 1278 [asyncResp](const boost::system::error_code ec) { 1279 if (ec) 1280 { 1281 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1282 << ec; 1283 messages::internalError(asyncResp->res); 1284 return; 1285 } 1286 messages::success(asyncResp->res); 1287 }, 1288 "xyz.openbmc_project.User.Manager", 1289 dbusObjectPath.c_str(), 1290 "org.freedesktop.DBus.Properties", "Set", 1291 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1292 std::variant<std::string>{priv}); 1293 } 1294 1295 if (locked) 1296 { 1297 // admin can unlock the account which is locked by 1298 // successive authentication failures but admin should not 1299 // be allowed to lock an account. 1300 if (*locked) 1301 { 1302 messages::propertyValueNotInList(asyncResp->res, "true", 1303 "Locked"); 1304 return; 1305 } 1306 1307 crow::connections::systemBus->async_method_call( 1308 [asyncResp](const boost::system::error_code ec) { 1309 if (ec) 1310 { 1311 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1312 << ec; 1313 messages::internalError(asyncResp->res); 1314 return; 1315 } 1316 messages::success(asyncResp->res); 1317 return; 1318 }, 1319 "xyz.openbmc_project.User.Manager", 1320 dbusObjectPath.c_str(), 1321 "org.freedesktop.DBus.Properties", "Set", 1322 "xyz.openbmc_project.User.Attributes", 1323 "UserLockedForFailedAttempt", 1324 sdbusplus::message::variant<bool>{*locked}); 1325 } 1326 }); 1327 } 1328 1329 void doDelete(crow::Response& res, const crow::Request& req, 1330 const std::vector<std::string>& params) override 1331 { 1332 auto asyncResp = std::make_shared<AsyncResp>(res); 1333 1334 if (params.size() != 1) 1335 { 1336 messages::internalError(asyncResp->res); 1337 return; 1338 } 1339 1340 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1341 1342 crow::connections::systemBus->async_method_call( 1343 [asyncResp, username{std::move(params[0])}]( 1344 const boost::system::error_code ec) { 1345 if (ec) 1346 { 1347 messages::resourceNotFound( 1348 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1349 username); 1350 return; 1351 } 1352 1353 messages::accountRemoved(asyncResp->res); 1354 }, 1355 "xyz.openbmc_project.User.Manager", userPath, 1356 "xyz.openbmc_project.Object.Delete", "Delete"); 1357 } 1358 }; 1359 1360 } // namespace redfish 1361