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