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