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::optional<nlohmann::json> authentication; 640 std::optional<nlohmann::json> ldapService; 641 std::optional<std::string> accountProviderType; 642 std::optional<std::vector<std::string>> serviceAddressList; 643 std::optional<bool> serviceEnabled; 644 std::optional<std::vector<std::string>> baseDNList; 645 std::optional<std::string> userNameAttribute; 646 std::optional<std::string> groupsAttribute; 647 std::optional<std::string> userName; 648 std::optional<std::string> password; 649 650 if (!json_util::readJson(input, asyncResp->res, "Authentication", 651 authentication, "LDAPService", ldapService, 652 "ServiceAddresses", serviceAddressList, 653 "AccountProviderType", accountProviderType, 654 "ServiceEnabled", serviceEnabled)) 655 { 656 return; 657 } 658 659 if (authentication) 660 { 661 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 662 password); 663 } 664 if (ldapService) 665 { 666 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 667 userNameAttribute, groupsAttribute); 668 } 669 if (accountProviderType) 670 { 671 messages::propertyNotWritable(asyncResp->res, 672 "AccountProviderType"); 673 } 674 if (serviceAddressList) 675 { 676 if ((*serviceAddressList).size() == 0) 677 { 678 messages::propertyValueNotInList(asyncResp->res, "[]", 679 "ServiceAddress"); 680 return; 681 } 682 } 683 if (baseDNList) 684 { 685 if ((*baseDNList).size() == 0) 686 { 687 messages::propertyValueNotInList(asyncResp->res, "[]", 688 "BaseDistinguishedNames"); 689 return; 690 } 691 } 692 693 // nothing to update, then return 694 if (!userName && !password && !serviceAddressList && !baseDNList && 695 !userNameAttribute && !groupsAttribute && !serviceEnabled) 696 { 697 return; 698 } 699 700 // Get the existing resource first then keep modifying 701 // whenever any property gets updated. 702 getLDAPConfigData(serverType, [this, asyncResp, userName, password, 703 baseDNList, userNameAttribute, 704 groupsAttribute, accountProviderType, 705 serviceAddressList, serviceEnabled]( 706 bool success, LDAPConfigData confData, 707 const std::string& serverType) { 708 if (!success) 709 { 710 messages::internalError(asyncResp->res); 711 return; 712 } 713 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType); 714 if (confData.serviceEnabled) 715 { 716 // Disable the service first and update the rest of 717 // the properties. 718 handleServiceEnablePatch(false, asyncResp, serverType, 719 ldapConfigObject); 720 } 721 722 if (serviceAddressList) 723 { 724 handleServiceAddressPatch(*serviceAddressList, asyncResp, 725 serverType, ldapConfigObject); 726 } 727 if (userName) 728 { 729 handleUserNamePatch(*userName, asyncResp, serverType, 730 ldapConfigObject); 731 } 732 if (password) 733 { 734 handlePasswordPatch(*password, asyncResp, serverType, 735 ldapConfigObject); 736 } 737 738 if (baseDNList) 739 { 740 handleBaseDNPatch(*baseDNList, asyncResp, serverType, 741 ldapConfigObject); 742 } 743 if (userNameAttribute) 744 { 745 handleUserNameAttrPatch(*userNameAttribute, asyncResp, 746 serverType, ldapConfigObject); 747 } 748 if (groupsAttribute) 749 { 750 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, 751 serverType, ldapConfigObject); 752 } 753 if (serviceEnabled) 754 { 755 // if user has given the value as true then enable 756 // the service. if user has given false then no-op 757 // as service is already stopped. 758 if (*serviceEnabled) 759 { 760 handleServiceEnablePatch(*serviceEnabled, asyncResp, 761 serverType, ldapConfigObject); 762 } 763 } 764 else 765 { 766 // if user has not given the service enabled value 767 // then revert it to the same state as it was 768 // before. 769 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 770 serverType, ldapConfigObject); 771 } 772 }); 773 } 774 775 void doGet(crow::Response& res, const crow::Request& req, 776 const std::vector<std::string>& params) override 777 { 778 auto asyncResp = std::make_shared<AsyncResp>(res); 779 res.jsonValue = { 780 {"@odata.context", "/redfish/v1/" 781 "$metadata#AccountService.AccountService"}, 782 {"@odata.id", "/redfish/v1/AccountService"}, 783 {"@odata.type", "#AccountService." 784 "v1_3_1.AccountService"}, 785 {"Id", "AccountService"}, 786 {"Name", "Account Service"}, 787 {"Description", "Account Service"}, 788 {"ServiceEnabled", true}, 789 {"MaxPasswordLength", 20}, 790 {"Accounts", 791 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 792 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}}; 793 794 crow::connections::systemBus->async_method_call( 795 [asyncResp]( 796 const boost::system::error_code ec, 797 const std::vector<std::pair< 798 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 799 propertiesList) { 800 if (ec) 801 { 802 messages::internalError(asyncResp->res); 803 return; 804 } 805 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 806 << "properties for AccountService"; 807 for (const std::pair<std::string, 808 std::variant<uint32_t, uint16_t, uint8_t>>& 809 property : propertiesList) 810 { 811 if (property.first == "MinPasswordLength") 812 { 813 const uint8_t* value = 814 std::get_if<uint8_t>(&property.second); 815 if (value != nullptr) 816 { 817 asyncResp->res.jsonValue["MinPasswordLength"] = 818 *value; 819 } 820 } 821 if (property.first == "AccountUnlockTimeout") 822 { 823 const uint32_t* value = 824 std::get_if<uint32_t>(&property.second); 825 if (value != nullptr) 826 { 827 asyncResp->res.jsonValue["AccountLockoutDuration"] = 828 *value; 829 } 830 } 831 if (property.first == "MaxLoginAttemptBeforeLockout") 832 { 833 const uint16_t* value = 834 std::get_if<uint16_t>(&property.second); 835 if (value != nullptr) 836 { 837 asyncResp->res 838 .jsonValue["AccountLockoutThreshold"] = *value; 839 } 840 } 841 } 842 }, 843 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 844 "org.freedesktop.DBus.Properties", "GetAll", 845 "xyz.openbmc_project.User.AccountPolicy"); 846 847 auto callback = [asyncResp](bool success, LDAPConfigData& confData, 848 const std::string& ldapType) { 849 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 850 }; 851 852 getLDAPConfigData("LDAP", callback); 853 getLDAPConfigData("ActiveDirectory", callback); 854 } 855 856 void doPatch(crow::Response& res, const crow::Request& req, 857 const std::vector<std::string>& params) override 858 { 859 auto asyncResp = std::make_shared<AsyncResp>(res); 860 861 std::optional<uint32_t> unlockTimeout; 862 std::optional<uint16_t> lockoutThreshold; 863 std::optional<uint16_t> minPasswordLength; 864 std::optional<uint16_t> maxPasswordLength; 865 std::optional<nlohmann::json> ldapObject; 866 867 if (!json_util::readJson(req, res, "AccountLockoutDuration", 868 unlockTimeout, "AccountLockoutThreshold", 869 lockoutThreshold, "MaxPasswordLength", 870 maxPasswordLength, "MinPasswordLength", 871 minPasswordLength)) 872 { 873 return; 874 } 875 876 if (minPasswordLength) 877 { 878 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 879 } 880 881 if (maxPasswordLength) 882 { 883 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 884 } 885 886 if (ldapObject) 887 { 888 handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP"); 889 } 890 891 if (unlockTimeout) 892 { 893 crow::connections::systemBus->async_method_call( 894 [asyncResp](const boost::system::error_code ec) { 895 if (ec) 896 { 897 messages::internalError(asyncResp->res); 898 return; 899 } 900 messages::success(asyncResp->res); 901 }, 902 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 903 "org.freedesktop.DBus.Properties", "Set", 904 "xyz.openbmc_project.User.AccountPolicy", 905 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 906 } 907 if (lockoutThreshold) 908 { 909 crow::connections::systemBus->async_method_call( 910 [asyncResp](const boost::system::error_code ec) { 911 if (ec) 912 { 913 messages::internalError(asyncResp->res); 914 return; 915 } 916 messages::success(asyncResp->res); 917 }, 918 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 919 "org.freedesktop.DBus.Properties", "Set", 920 "xyz.openbmc_project.User.AccountPolicy", 921 "MaxLoginAttemptBeforeLockout", 922 std::variant<uint16_t>(*lockoutThreshold)); 923 } 924 } 925 }; 926 927 class AccountsCollection : public Node 928 { 929 public: 930 AccountsCollection(CrowApp& app) : 931 Node(app, "/redfish/v1/AccountService/Accounts/") 932 { 933 entityPrivileges = { 934 {boost::beast::http::verb::get, 935 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 936 {boost::beast::http::verb::head, {{"Login"}}}, 937 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 938 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 939 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 940 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 941 } 942 943 private: 944 void doGet(crow::Response& res, const crow::Request& req, 945 const std::vector<std::string>& params) override 946 { 947 auto asyncResp = std::make_shared<AsyncResp>(res); 948 res.jsonValue = {{"@odata.context", 949 "/redfish/v1/" 950 "$metadata#ManagerAccountCollection." 951 "ManagerAccountCollection"}, 952 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 953 {"@odata.type", "#ManagerAccountCollection." 954 "ManagerAccountCollection"}, 955 {"Name", "Accounts Collection"}, 956 {"Description", "BMC User Accounts"}}; 957 958 crow::connections::systemBus->async_method_call( 959 [asyncResp](const boost::system::error_code ec, 960 const ManagedObjectType& users) { 961 if (ec) 962 { 963 messages::internalError(asyncResp->res); 964 return; 965 } 966 967 nlohmann::json& memberArray = 968 asyncResp->res.jsonValue["Members"]; 969 memberArray = nlohmann::json::array(); 970 971 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 972 for (auto& user : users) 973 { 974 const std::string& path = 975 static_cast<const std::string&>(user.first); 976 std::size_t lastIndex = path.rfind("/"); 977 if (lastIndex == std::string::npos) 978 { 979 lastIndex = 0; 980 } 981 else 982 { 983 lastIndex += 1; 984 } 985 memberArray.push_back( 986 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 987 path.substr(lastIndex)}}); 988 } 989 }, 990 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 991 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 992 } 993 void doPost(crow::Response& res, const crow::Request& req, 994 const std::vector<std::string>& params) override 995 { 996 auto asyncResp = std::make_shared<AsyncResp>(res); 997 998 std::string username; 999 std::string password; 1000 std::optional<std::string> roleId("User"); 1001 std::optional<bool> enabled = true; 1002 if (!json_util::readJson(req, res, "UserName", username, "Password", 1003 password, "RoleId", roleId, "Enabled", 1004 enabled)) 1005 { 1006 return; 1007 } 1008 1009 std::string priv = getRoleIdFromPrivilege(*roleId); 1010 if (priv.empty()) 1011 { 1012 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1013 return; 1014 } 1015 roleId = priv; 1016 1017 crow::connections::systemBus->async_method_call( 1018 [asyncResp, username, password{std::move(password)}]( 1019 const boost::system::error_code ec) { 1020 if (ec) 1021 { 1022 messages::resourceAlreadyExists( 1023 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1024 "UserName", username); 1025 return; 1026 } 1027 1028 if (!pamUpdatePassword(username, password)) 1029 { 1030 // At this point we have a user that's been created, but the 1031 // password set failed. Something is wrong, so delete the 1032 // user that we've already created 1033 crow::connections::systemBus->async_method_call( 1034 [asyncResp](const boost::system::error_code ec) { 1035 if (ec) 1036 { 1037 messages::internalError(asyncResp->res); 1038 return; 1039 } 1040 1041 messages::invalidObject(asyncResp->res, "Password"); 1042 }, 1043 "xyz.openbmc_project.User.Manager", 1044 "/xyz/openbmc_project/user/" + username, 1045 "xyz.openbmc_project.Object.Delete", "Delete"); 1046 1047 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1048 return; 1049 } 1050 1051 messages::created(asyncResp->res); 1052 asyncResp->res.addHeader( 1053 "Location", 1054 "/redfish/v1/AccountService/Accounts/" + username); 1055 }, 1056 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1057 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1058 std::array<const char*, 4>{"ipmi", "redfish", "ssh", "web"}, 1059 *roleId, *enabled); 1060 } 1061 }; 1062 1063 class ManagerAccount : public Node 1064 { 1065 public: 1066 ManagerAccount(CrowApp& app) : 1067 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1068 { 1069 entityPrivileges = { 1070 {boost::beast::http::verb::get, 1071 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1072 {boost::beast::http::verb::head, {{"Login"}}}, 1073 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1074 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1075 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1076 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1077 } 1078 1079 private: 1080 void doGet(crow::Response& res, const crow::Request& req, 1081 const std::vector<std::string>& params) override 1082 { 1083 res.jsonValue = { 1084 {"@odata.context", 1085 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 1086 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 1087 {"Name", "User Account"}, 1088 {"Description", "User Account"}, 1089 {"Password", nullptr}, 1090 {"RoleId", "Administrator"}}; 1091 1092 auto asyncResp = std::make_shared<AsyncResp>(res); 1093 1094 if (params.size() != 1) 1095 { 1096 messages::internalError(asyncResp->res); 1097 return; 1098 } 1099 1100 crow::connections::systemBus->async_method_call( 1101 [asyncResp, accountName{std::string(params[0])}]( 1102 const boost::system::error_code ec, 1103 const ManagedObjectType& users) { 1104 if (ec) 1105 { 1106 messages::internalError(asyncResp->res); 1107 return; 1108 } 1109 auto userIt = users.begin(); 1110 1111 for (; userIt != users.end(); userIt++) 1112 { 1113 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1114 { 1115 break; 1116 } 1117 } 1118 if (userIt == users.end()) 1119 { 1120 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1121 accountName); 1122 return; 1123 } 1124 for (const auto& interface : userIt->second) 1125 { 1126 if (interface.first == 1127 "xyz.openbmc_project.User.Attributes") 1128 { 1129 for (const auto& property : interface.second) 1130 { 1131 if (property.first == "UserEnabled") 1132 { 1133 const bool* userEnabled = 1134 std::get_if<bool>(&property.second); 1135 if (userEnabled == nullptr) 1136 { 1137 BMCWEB_LOG_ERROR 1138 << "UserEnabled wasn't a bool"; 1139 messages::internalError(asyncResp->res); 1140 return; 1141 } 1142 asyncResp->res.jsonValue["Enabled"] = 1143 *userEnabled; 1144 } 1145 else if (property.first == 1146 "UserLockedForFailedAttempt") 1147 { 1148 const bool* userLocked = 1149 std::get_if<bool>(&property.second); 1150 if (userLocked == nullptr) 1151 { 1152 BMCWEB_LOG_ERROR << "UserLockedForF" 1153 "ailedAttempt " 1154 "wasn't a bool"; 1155 messages::internalError(asyncResp->res); 1156 return; 1157 } 1158 asyncResp->res.jsonValue["Locked"] = 1159 *userLocked; 1160 asyncResp->res.jsonValue 1161 ["Locked@Redfish.AllowableValues"] = { 1162 "false"}; 1163 } 1164 else if (property.first == "UserPrivilege") 1165 { 1166 const std::string* userRolePtr = 1167 std::get_if<std::string>(&property.second); 1168 if (userRolePtr == nullptr) 1169 { 1170 BMCWEB_LOG_ERROR 1171 << "UserPrivilege wasn't a " 1172 "string"; 1173 messages::internalError(asyncResp->res); 1174 return; 1175 } 1176 std::string priv = 1177 getPrivilegeFromRoleId(*userRolePtr); 1178 if (priv.empty()) 1179 { 1180 BMCWEB_LOG_ERROR << "Invalid user role"; 1181 messages::internalError(asyncResp->res); 1182 return; 1183 } 1184 asyncResp->res.jsonValue["RoleId"] = priv; 1185 1186 asyncResp->res.jsonValue["Links"]["Role"] = { 1187 {"@odata.id", "/redfish/v1/AccountService/" 1188 "Roles/" + 1189 priv}}; 1190 } 1191 } 1192 } 1193 } 1194 1195 asyncResp->res.jsonValue["@odata.id"] = 1196 "/redfish/v1/AccountService/Accounts/" + accountName; 1197 asyncResp->res.jsonValue["Id"] = accountName; 1198 asyncResp->res.jsonValue["UserName"] = accountName; 1199 }, 1200 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1201 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1202 } 1203 1204 void doPatch(crow::Response& res, const crow::Request& req, 1205 const std::vector<std::string>& params) override 1206 { 1207 auto asyncResp = std::make_shared<AsyncResp>(res); 1208 if (params.size() != 1) 1209 { 1210 messages::internalError(asyncResp->res); 1211 return; 1212 } 1213 1214 std::optional<std::string> newUserName; 1215 std::optional<std::string> password; 1216 std::optional<bool> enabled; 1217 std::optional<std::string> roleId; 1218 std::optional<bool> locked; 1219 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1220 password, "RoleId", roleId, "Enabled", enabled, 1221 "Locked", locked)) 1222 { 1223 return; 1224 } 1225 1226 const std::string& username = params[0]; 1227 1228 if (!newUserName) 1229 { 1230 // If the username isn't being updated, we can update the properties 1231 // directly 1232 updateUserProperties(asyncResp, username, password, enabled, roleId, 1233 locked); 1234 return; 1235 } 1236 else 1237 { 1238 crow::connections::systemBus->async_method_call( 1239 [this, asyncResp, username, password(std::move(password)), 1240 roleId(std::move(roleId)), enabled(std::move(enabled)), 1241 newUser{std::string(*newUserName)}, locked(std::move(locked))]( 1242 const boost::system::error_code ec) { 1243 if (ec) 1244 { 1245 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 1246 messages::resourceNotFound( 1247 asyncResp->res, 1248 "#ManagerAccount.v1_0_3.ManagerAccount", username); 1249 return; 1250 } 1251 1252 updateUserProperties(asyncResp, newUser, password, enabled, 1253 roleId, locked); 1254 }, 1255 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1256 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1257 *newUserName); 1258 } 1259 } 1260 1261 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1262 const std::string& username, 1263 std::optional<std::string> password, 1264 std::optional<bool> enabled, 1265 std::optional<std::string> roleId, 1266 std::optional<bool> locked) 1267 { 1268 if (password) 1269 { 1270 if (!pamUpdatePassword(username, *password)) 1271 { 1272 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1273 messages::internalError(asyncResp->res); 1274 return; 1275 } 1276 } 1277 1278 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1279 dbus::utility::escapePathForDbus(dbusObjectPath); 1280 1281 dbus::utility::checkDbusPathExists( 1282 dbusObjectPath, 1283 [dbusObjectPath(std::move(dbusObjectPath)), username, 1284 password(std::move(password)), roleId(std::move(roleId)), 1285 enabled(std::move(enabled)), locked(std::move(locked)), 1286 asyncResp{std::move(asyncResp)}](int rc) { 1287 if (!rc) 1288 { 1289 messages::invalidObject(asyncResp->res, username.c_str()); 1290 return; 1291 } 1292 if (enabled) 1293 { 1294 crow::connections::systemBus->async_method_call( 1295 [asyncResp](const boost::system::error_code ec) { 1296 if (ec) 1297 { 1298 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1299 << ec; 1300 messages::internalError(asyncResp->res); 1301 return; 1302 } 1303 messages::success(asyncResp->res); 1304 return; 1305 }, 1306 "xyz.openbmc_project.User.Manager", 1307 dbusObjectPath.c_str(), 1308 "org.freedesktop.DBus.Properties", "Set", 1309 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1310 std::variant<bool>{*enabled}); 1311 } 1312 1313 if (roleId) 1314 { 1315 std::string priv = getRoleIdFromPrivilege(*roleId); 1316 if (priv.empty()) 1317 { 1318 messages::propertyValueNotInList(asyncResp->res, 1319 *roleId, "RoleId"); 1320 return; 1321 } 1322 1323 crow::connections::systemBus->async_method_call( 1324 [asyncResp](const boost::system::error_code ec) { 1325 if (ec) 1326 { 1327 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1328 << ec; 1329 messages::internalError(asyncResp->res); 1330 return; 1331 } 1332 messages::success(asyncResp->res); 1333 }, 1334 "xyz.openbmc_project.User.Manager", 1335 dbusObjectPath.c_str(), 1336 "org.freedesktop.DBus.Properties", "Set", 1337 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1338 std::variant<std::string>{priv}); 1339 } 1340 1341 if (locked) 1342 { 1343 // admin can unlock the account which is locked by 1344 // successive authentication failures but admin should not 1345 // be allowed to lock an account. 1346 if (*locked) 1347 { 1348 messages::propertyValueNotInList(asyncResp->res, "true", 1349 "Locked"); 1350 return; 1351 } 1352 1353 crow::connections::systemBus->async_method_call( 1354 [asyncResp](const boost::system::error_code ec) { 1355 if (ec) 1356 { 1357 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1358 << ec; 1359 messages::internalError(asyncResp->res); 1360 return; 1361 } 1362 messages::success(asyncResp->res); 1363 return; 1364 }, 1365 "xyz.openbmc_project.User.Manager", 1366 dbusObjectPath.c_str(), 1367 "org.freedesktop.DBus.Properties", "Set", 1368 "xyz.openbmc_project.User.Attributes", 1369 "UserLockedForFailedAttempt", 1370 sdbusplus::message::variant<bool>{*locked}); 1371 } 1372 }); 1373 } 1374 1375 void doDelete(crow::Response& res, const crow::Request& req, 1376 const std::vector<std::string>& params) override 1377 { 1378 auto asyncResp = std::make_shared<AsyncResp>(res); 1379 1380 if (params.size() != 1) 1381 { 1382 messages::internalError(asyncResp->res); 1383 return; 1384 } 1385 1386 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1387 1388 crow::connections::systemBus->async_method_call( 1389 [asyncResp, username{std::move(params[0])}]( 1390 const boost::system::error_code ec) { 1391 if (ec) 1392 { 1393 messages::resourceNotFound( 1394 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1395 username); 1396 return; 1397 } 1398 1399 messages::accountRemoved(asyncResp->res); 1400 }, 1401 "xyz.openbmc_project.User.Manager", userPath, 1402 "xyz.openbmc_project.Object.Delete", "Delete"); 1403 } 1404 }; 1405 1406 } // namespace redfish 1407