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