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