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* ldapPrivMapperInterface = 41 "xyz.openbmc_project.User.PrivilegeMapper"; 42 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; 43 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties"; 44 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper"; 45 constexpr const char* mapperObjectPath = "/xyz/openbmc_project/object_mapper"; 46 constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper"; 47 48 struct LDAPRoleMapData 49 { 50 std::string groupName; 51 std::string privilege; 52 }; 53 54 struct LDAPConfigData 55 { 56 std::string uri{}; 57 std::string bindDN{}; 58 std::string baseDN{}; 59 std::string searchScope{}; 60 std::string serverType{}; 61 bool serviceEnabled = false; 62 std::string userNameAttribute{}; 63 std::string groupAttribute{}; 64 std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList; 65 }; 66 67 using ManagedObjectType = std::vector<std::pair< 68 sdbusplus::message::object_path, 69 boost::container::flat_map< 70 std::string, boost::container::flat_map< 71 std::string, std::variant<bool, std::string>>>>>; 72 using GetObjectType = 73 std::vector<std::pair<std::string, std::vector<std::string>>>; 74 75 inline std::string getRoleIdFromPrivilege(std::string_view role) 76 { 77 if (role == "priv-admin") 78 { 79 return "Administrator"; 80 } 81 else if (role == "priv-callback") 82 { 83 return "Callback"; 84 } 85 else if (role == "priv-user") 86 { 87 return "ReadOnly"; 88 } 89 else if (role == "priv-operator") 90 { 91 return "Operator"; 92 } 93 return ""; 94 } 95 inline std::string getPrivilegeFromRoleId(std::string_view role) 96 { 97 if (role == "Administrator") 98 { 99 return "priv-admin"; 100 } 101 else if (role == "Callback") 102 { 103 return "priv-callback"; 104 } 105 else if (role == "ReadOnly") 106 { 107 return "priv-user"; 108 } 109 else if (role == "Operator") 110 { 111 return "priv-operator"; 112 } 113 return ""; 114 } 115 116 void userErrorMessageHandler(const sd_bus_error* e, 117 std::shared_ptr<AsyncResp> asyncResp, 118 const std::string& newUser, 119 const std::string& username) 120 { 121 const char* errorMessage = e->name; 122 if (e == nullptr) 123 { 124 messages::internalError(asyncResp->res); 125 return; 126 } 127 128 if (strcmp(errorMessage, 129 "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0) 130 { 131 messages::resourceAlreadyExists(asyncResp->res, 132 "#ManagerAccount.v1_0_3.ManagerAccount", 133 "UserName", newUser); 134 } 135 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 136 "UserNameDoesNotExist") == 0) 137 { 138 messages::resourceNotFound( 139 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", username); 140 } 141 else if (strcmp(errorMessage, 142 "xyz.openbmc_project.Common.Error.InvalidArgument") == 0) 143 { 144 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 145 } 146 else if (strcmp(errorMessage, 147 "xyz.openbmc_project.User.Common.Error.NoResource") == 0) 148 { 149 messages::createLimitReachedForResource(asyncResp->res); 150 } 151 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 152 "UserNameGroupFail") == 0) 153 { 154 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 155 } 156 else 157 { 158 messages::internalError(asyncResp->res); 159 } 160 161 return; 162 } 163 164 void parseLDAPConfigData(nlohmann::json& json_response, 165 const LDAPConfigData& confData, 166 const std::string& ldapType) 167 { 168 std::string service = 169 (ldapType == "LDAP") ? "LDAPService" : "ActiveDirectoryService"; 170 nlohmann::json ldap = { 171 {"AccountProviderType", service}, 172 {"ServiceEnabled", confData.serviceEnabled}, 173 {"ServiceAddresses", nlohmann::json::array({confData.uri})}, 174 {"Authentication", 175 {{"AuthenticationType", "UsernameAndPassword"}, 176 {"Username", confData.bindDN}, 177 {"Password", nullptr}}}, 178 {"LDAPService", 179 {{"SearchSettings", 180 {{"BaseDistinguishedNames", 181 nlohmann::json::array({confData.baseDN})}, 182 {"UsernameAttribute", confData.userNameAttribute}, 183 {"GroupsAttribute", confData.groupAttribute}}}}}, 184 }; 185 186 json_response[ldapType].update(std::move(ldap)); 187 188 nlohmann::json& roleMapArray = json_response[ldapType]["RemoteRoleMapping"]; 189 roleMapArray = nlohmann::json::array(); 190 for (auto& obj : confData.groupRoleList) 191 { 192 BMCWEB_LOG_DEBUG << "Pushing the data groupName=" 193 << obj.second.groupName << "\n"; 194 roleMapArray.push_back( 195 {nlohmann::json::array({"RemoteGroup", obj.second.groupName}), 196 nlohmann::json::array( 197 {"LocalRole", getRoleIdFromPrivilege(obj.second.privilege)})}); 198 } 199 } 200 201 /** 202 * @brief validates given JSON input and then calls appropriate method to 203 * create, to delete or to set Rolemapping object based on the given input. 204 * 205 */ 206 static void handleRoleMapPatch( 207 const std::shared_ptr<AsyncResp>& asyncResp, 208 const std::vector<std::pair<std::string, LDAPRoleMapData>>& roleMapObjData, 209 const std::string& serverType, std::vector<nlohmann::json>& input) 210 { 211 for (size_t index = 0; index < input.size(); index++) 212 { 213 nlohmann::json& thisJson = input[index]; 214 215 if (thisJson.is_null()) 216 { 217 // delete the existing object 218 if (index < roleMapObjData.size()) 219 { 220 crow::connections::systemBus->async_method_call( 221 [asyncResp, roleMapObjData, serverType, 222 index](const boost::system::error_code ec) { 223 if (ec) 224 { 225 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 226 messages::internalError(asyncResp->res); 227 return; 228 } 229 asyncResp->res 230 .jsonValue[serverType]["RemoteRoleMapping"][index] = 231 nullptr; 232 }, 233 ldapDbusService, roleMapObjData[index].first, 234 "xyz.openbmc_project.Object.Delete", "Delete"); 235 } 236 else 237 { 238 BMCWEB_LOG_ERROR << "Can't delete the object"; 239 messages::propertyValueTypeError( 240 asyncResp->res, thisJson.dump(), 241 "RemoteRoleMapping/" + std::to_string(index)); 242 return; 243 } 244 } 245 else if (thisJson.empty()) 246 { 247 // Don't do anything for the empty objects,parse next json 248 // eg {"RemoteRoleMapping",[{}]} 249 } 250 else 251 { 252 // update/create the object 253 std::optional<std::string> remoteGroup; 254 std::optional<std::string> localRole; 255 256 if (!json_util::readJson(thisJson, asyncResp->res, "RemoteGroup", 257 remoteGroup, "LocalRole", localRole)) 258 { 259 continue; 260 } 261 262 // Update existing RoleMapping Object 263 if (index < roleMapObjData.size()) 264 { 265 BMCWEB_LOG_DEBUG << "Update Role Map Object"; 266 // If "RemoteGroup" info is provided 267 if (remoteGroup) 268 { 269 crow::connections::systemBus->async_method_call( 270 [asyncResp, roleMapObjData, serverType, index, 271 remoteGroup](const boost::system::error_code ec) { 272 if (ec) 273 { 274 BMCWEB_LOG_ERROR << "DBUS response error: " 275 << ec; 276 messages::internalError(asyncResp->res); 277 return; 278 } 279 asyncResp->res 280 .jsonValue[serverType]["RemoteRoleMapping"] 281 [index]["RemoteGroup"] = *remoteGroup; 282 }, 283 ldapDbusService, roleMapObjData[index].first, 284 propertyInterface, "Set", 285 "xyz.openbmc_project.User.PrivilegeMapperEntry", 286 "GroupName", 287 std::variant<std::string>(std::move(*remoteGroup))); 288 } 289 290 // If "LocalRole" info is provided 291 if (localRole) 292 { 293 crow::connections::systemBus->async_method_call( 294 [asyncResp, roleMapObjData, serverType, index, 295 localRole](const boost::system::error_code ec) { 296 if (ec) 297 { 298 BMCWEB_LOG_ERROR << "DBUS response error: " 299 << ec; 300 messages::internalError(asyncResp->res); 301 return; 302 } 303 asyncResp->res 304 .jsonValue[serverType]["RemoteRoleMapping"] 305 [index]["LocalRole"] = *localRole; 306 }, 307 ldapDbusService, roleMapObjData[index].first, 308 propertyInterface, "Set", 309 "xyz.openbmc_project.User.PrivilegeMapperEntry", 310 "Privilege", 311 std::variant<std::string>( 312 getPrivilegeFromRoleId(std::move(*localRole)))); 313 } 314 } 315 // Create a new RoleMapping Object. 316 else 317 { 318 BMCWEB_LOG_DEBUG 319 << "setRoleMappingProperties: Creating new Object"; 320 std::string pathString = 321 "RemoteRoleMapping/" + std::to_string(index); 322 323 if (!localRole) 324 { 325 messages::propertyMissing(asyncResp->res, 326 pathString + "/LocalRole"); 327 continue; 328 } 329 if (!remoteGroup) 330 { 331 messages::propertyMissing(asyncResp->res, 332 pathString + "/RemoteGroup"); 333 continue; 334 } 335 336 std::string dbusObjectPath; 337 if (serverType == "ActiveDirectory") 338 { 339 dbusObjectPath = ADConfigObject; 340 } 341 else if (serverType == "LDAP") 342 { 343 dbusObjectPath = ldapConfigObject; 344 } 345 346 BMCWEB_LOG_DEBUG << "Remote Group=" << *remoteGroup 347 << ",LocalRole=" << *localRole; 348 349 crow::connections::systemBus->async_method_call( 350 [asyncResp, serverType, localRole, 351 remoteGroup](const boost::system::error_code ec) { 352 if (ec) 353 { 354 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 355 messages::internalError(asyncResp->res); 356 return; 357 } 358 nlohmann::json& remoteRoleJson = 359 asyncResp->res 360 .jsonValue[serverType]["RemoteRoleMapping"]; 361 remoteRoleJson.push_back( 362 {{"LocalRole", *localRole}, 363 {"RemoteGroup", *remoteGroup}}); 364 }, 365 ldapDbusService, dbusObjectPath, ldapPrivMapperInterface, 366 "Create", std::move(*remoteGroup), 367 getPrivilegeFromRoleId(std::move(*localRole))); 368 } 369 } 370 } 371 } 372 373 /** 374 * Function that retrieves all properties for LDAP config object 375 * into JSON 376 */ 377 template <typename CallbackFunc> 378 inline void getLDAPConfigData(const std::string& ldapType, 379 CallbackFunc&& callback) 380 { 381 382 const std::array<const char*, 2> interfaces = {ldapEnableInterface, 383 ldapConfigInterface}; 384 385 crow::connections::systemBus->async_method_call( 386 [callback, ldapType](const boost::system::error_code ec, 387 const GetObjectType& resp) { 388 LDAPConfigData confData{}; 389 if (ec || resp.empty()) 390 { 391 BMCWEB_LOG_ERROR << "DBUS response error during getting of " 392 "service name: " 393 << ec; 394 callback(false, confData, ldapType); 395 return; 396 } 397 std::string service = resp.begin()->first; 398 crow::connections::systemBus->async_method_call( 399 [callback, ldapType](const boost::system::error_code error_code, 400 const ManagedObjectType& ldapObjects) { 401 LDAPConfigData confData{}; 402 if (error_code) 403 { 404 callback(false, confData, ldapType); 405 BMCWEB_LOG_ERROR << "D-Bus responses error: " 406 << error_code; 407 return; 408 } 409 410 std::string ldapDbusType; 411 std::string searchString; 412 413 if (ldapType == "LDAP") 414 { 415 ldapDbusType = "xyz.openbmc_project.User.Ldap.Config." 416 "Type.OpenLdap"; 417 searchString = "openldap"; 418 } 419 else if (ldapType == "ActiveDirectory") 420 { 421 ldapDbusType = 422 "xyz.openbmc_project.User.Ldap.Config.Type." 423 "ActiveDirectory"; 424 searchString = "active_directory"; 425 } 426 else 427 { 428 BMCWEB_LOG_ERROR 429 << "Can't get the DbusType for the given type=" 430 << ldapType; 431 callback(false, confData, ldapType); 432 return; 433 } 434 435 std::string ldapEnableInterfaceStr = ldapEnableInterface; 436 std::string ldapConfigInterfaceStr = ldapConfigInterface; 437 438 for (const auto& object : ldapObjects) 439 { 440 // let's find the object whose ldap type is equal to the 441 // given type 442 if (object.first.str.find(searchString) == 443 std::string::npos) 444 { 445 continue; 446 } 447 448 for (const auto& interface : object.second) 449 { 450 if (interface.first == ldapEnableInterfaceStr) 451 { 452 // rest of the properties are string. 453 for (const auto& property : interface.second) 454 { 455 if (property.first == "Enabled") 456 { 457 const bool* value = 458 std::get_if<bool>(&property.second); 459 if (value == nullptr) 460 { 461 continue; 462 } 463 confData.serviceEnabled = *value; 464 break; 465 } 466 } 467 } 468 else if (interface.first == ldapConfigInterfaceStr) 469 { 470 471 for (const auto& property : interface.second) 472 { 473 const std::string* strValue = 474 std::get_if<std::string>( 475 &property.second); 476 if (strValue == nullptr) 477 { 478 continue; 479 } 480 if (property.first == "LDAPServerURI") 481 { 482 confData.uri = *strValue; 483 } 484 else if (property.first == "LDAPBindDN") 485 { 486 confData.bindDN = *strValue; 487 } 488 else if (property.first == "LDAPBaseDN") 489 { 490 confData.baseDN = *strValue; 491 } 492 else if (property.first == 493 "LDAPSearchScope") 494 { 495 confData.searchScope = *strValue; 496 } 497 else if (property.first == 498 "GroupNameAttribute") 499 { 500 confData.groupAttribute = *strValue; 501 } 502 else if (property.first == 503 "UserNameAttribute") 504 { 505 confData.userNameAttribute = *strValue; 506 } 507 else if (property.first == "LDAPType") 508 { 509 confData.serverType = *strValue; 510 } 511 } 512 } 513 else if (interface.first == 514 "xyz.openbmc_project.User." 515 "PrivilegeMapperEntry") 516 { 517 LDAPRoleMapData roleMapData{}; 518 for (const auto& property : interface.second) 519 { 520 const std::string* strValue = 521 std::get_if<std::string>( 522 &property.second); 523 524 if (strValue == nullptr) 525 { 526 continue; 527 } 528 529 if (property.first == "GroupName") 530 { 531 roleMapData.groupName = *strValue; 532 } 533 else if (property.first == "Privilege") 534 { 535 roleMapData.privilege = *strValue; 536 } 537 } 538 539 confData.groupRoleList.emplace_back( 540 object.first.str, roleMapData); 541 } 542 } 543 } 544 callback(true, confData, ldapType); 545 }, 546 service, ldapRootObject, dbusObjManagerIntf, 547 "GetManagedObjects"); 548 }, 549 mapperBusName, mapperObjectPath, mapperIntf, "GetObject", 550 ldapConfigObject, interfaces); 551 } 552 553 class AccountService : public Node 554 { 555 public: 556 AccountService(CrowApp& app) : 557 Node(app, "/redfish/v1/AccountService/"), app(app) 558 { 559 entityPrivileges = { 560 {boost::beast::http::verb::get, 561 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 562 {boost::beast::http::verb::head, {{"Login"}}}, 563 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 564 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 565 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 566 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 567 } 568 569 private: 570 /** 571 * @brief parses the authentication section under the LDAP 572 * @param input JSON data 573 * @param asyncResp pointer to the JSON response 574 * @param userName userName to be filled from the given JSON. 575 * @param password password to be filled from the given JSON. 576 */ 577 void 578 parseLDAPAuthenticationJson(nlohmann::json input, 579 const std::shared_ptr<AsyncResp>& asyncResp, 580 std::optional<std::string>& username, 581 std::optional<std::string>& password) 582 { 583 std::optional<std::string> authType; 584 585 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 586 authType, "Username", username, "Password", 587 password)) 588 { 589 return; 590 } 591 if (!authType) 592 { 593 return; 594 } 595 if (*authType != "UsernameAndPassword") 596 { 597 messages::propertyValueNotInList(asyncResp->res, *authType, 598 "AuthenticationType"); 599 return; 600 } 601 } 602 /** 603 * @brief parses the LDAPService section under the LDAP 604 * @param input JSON data 605 * @param asyncResp pointer to the JSON response 606 * @param baseDNList baseDN to be filled from the given JSON. 607 * @param userNameAttribute userName to be filled from the given JSON. 608 * @param groupaAttribute password to be filled from the given JSON. 609 */ 610 611 void parseLDAPServiceJson( 612 nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp, 613 std::optional<std::vector<std::string>>& baseDNList, 614 std::optional<std::string>& userNameAttribute, 615 std::optional<std::string>& groupsAttribute) 616 { 617 std::optional<nlohmann::json> searchSettings; 618 619 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 620 searchSettings)) 621 { 622 return; 623 } 624 if (!searchSettings) 625 { 626 return; 627 } 628 if (!json_util::readJson(*searchSettings, asyncResp->res, 629 "BaseDistinguishedNames", baseDNList, 630 "UsernameAttribute", userNameAttribute, 631 "GroupsAttribute", groupsAttribute)) 632 { 633 return; 634 } 635 } 636 /** 637 * @brief updates the LDAP server address and updates the 638 json response with the new value. 639 * @param serviceAddressList address to be updated. 640 * @param asyncResp pointer to the JSON response 641 * @param ldapServerElementName Type of LDAP 642 server(openLDAP/ActiveDirectory) 643 */ 644 645 void handleServiceAddressPatch( 646 const std::vector<std::string>& serviceAddressList, 647 const std::shared_ptr<AsyncResp>& asyncResp, 648 const std::string& ldapServerElementName, 649 const std::string& ldapConfigObject) 650 { 651 crow::connections::systemBus->async_method_call( 652 [asyncResp, ldapServerElementName, 653 serviceAddressList](const boost::system::error_code ec) { 654 if (ec) 655 { 656 BMCWEB_LOG_DEBUG 657 << "Error Occured in updating the service address"; 658 messages::internalError(asyncResp->res); 659 return; 660 } 661 std::vector<std::string> modifiedserviceAddressList = { 662 serviceAddressList.front()}; 663 asyncResp->res 664 .jsonValue[ldapServerElementName]["ServiceAddresses"] = 665 modifiedserviceAddressList; 666 if ((serviceAddressList).size() > 1) 667 { 668 messages::propertyValueModified(asyncResp->res, 669 "ServiceAddresses", 670 serviceAddressList.front()); 671 } 672 BMCWEB_LOG_DEBUG << "Updated the service address"; 673 }, 674 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 675 ldapConfigInterface, "LDAPServerURI", 676 std::variant<std::string>(serviceAddressList.front())); 677 } 678 /** 679 * @brief updates the LDAP Bind DN and updates the 680 json response with the new value. 681 * @param username name of the user which needs to be updated. 682 * @param asyncResp pointer to the JSON response 683 * @param ldapServerElementName Type of LDAP 684 server(openLDAP/ActiveDirectory) 685 */ 686 687 void handleUserNamePatch(const std::string& username, 688 const std::shared_ptr<AsyncResp>& asyncResp, 689 const std::string& ldapServerElementName, 690 const std::string& ldapConfigObject) 691 { 692 crow::connections::systemBus->async_method_call( 693 [asyncResp, username, 694 ldapServerElementName](const boost::system::error_code ec) { 695 if (ec) 696 { 697 BMCWEB_LOG_DEBUG 698 << "Error occured in updating the username"; 699 messages::internalError(asyncResp->res); 700 return; 701 } 702 asyncResp->res.jsonValue[ldapServerElementName] 703 ["Authentication"]["Username"] = 704 username; 705 BMCWEB_LOG_DEBUG << "Updated the username"; 706 }, 707 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 708 ldapConfigInterface, "LDAPBindDN", 709 std::variant<std::string>(username)); 710 } 711 712 /** 713 * @brief updates the LDAP password 714 * @param password : ldap password which needs to be updated. 715 * @param asyncResp pointer to the JSON response 716 * @param ldapServerElementName Type of LDAP 717 * server(openLDAP/ActiveDirectory) 718 */ 719 720 void handlePasswordPatch(const std::string& password, 721 const std::shared_ptr<AsyncResp>& asyncResp, 722 const std::string& ldapServerElementName, 723 const std::string& ldapConfigObject) 724 { 725 crow::connections::systemBus->async_method_call( 726 [asyncResp, password, 727 ldapServerElementName](const boost::system::error_code ec) { 728 if (ec) 729 { 730 BMCWEB_LOG_DEBUG 731 << "Error occured in updating the password"; 732 messages::internalError(asyncResp->res); 733 return; 734 } 735 asyncResp->res.jsonValue[ldapServerElementName] 736 ["Authentication"]["Password"] = ""; 737 BMCWEB_LOG_DEBUG << "Updated the password"; 738 }, 739 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 740 ldapConfigInterface, "LDAPBindDNPassword", 741 std::variant<std::string>(password)); 742 } 743 744 /** 745 * @brief updates the LDAP BaseDN and updates the 746 json response with the new value. 747 * @param baseDNList baseDN list which needs to be updated. 748 * @param asyncResp pointer to the JSON response 749 * @param ldapServerElementName Type of LDAP 750 server(openLDAP/ActiveDirectory) 751 */ 752 753 void handleBaseDNPatch(const std::vector<std::string>& baseDNList, 754 const std::shared_ptr<AsyncResp>& asyncResp, 755 const std::string& ldapServerElementName, 756 const std::string& ldapConfigObject) 757 { 758 crow::connections::systemBus->async_method_call( 759 [asyncResp, baseDNList, 760 ldapServerElementName](const boost::system::error_code ec) { 761 if (ec) 762 { 763 BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN"; 764 messages::internalError(asyncResp->res); 765 return; 766 } 767 auto& serverTypeJson = 768 asyncResp->res.jsonValue[ldapServerElementName]; 769 auto& searchSettingsJson = 770 serverTypeJson["LDAPService"]["SearchSettings"]; 771 std::vector<std::string> modifiedBaseDNList = { 772 baseDNList.front()}; 773 searchSettingsJson["BaseDistinguishedNames"] = 774 modifiedBaseDNList; 775 if (baseDNList.size() > 1) 776 { 777 messages::propertyValueModified(asyncResp->res, 778 "BaseDistinguishedNames", 779 baseDNList.front()); 780 } 781 BMCWEB_LOG_DEBUG << "Updated the base DN"; 782 }, 783 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 784 ldapConfigInterface, "LDAPBaseDN", 785 std::variant<std::string>(baseDNList.front())); 786 } 787 /** 788 * @brief updates the LDAP user name attribute and updates the 789 json response with the new value. 790 * @param userNameAttribute attribute to be updated. 791 * @param asyncResp pointer to the JSON response 792 * @param ldapServerElementName Type of LDAP 793 server(openLDAP/ActiveDirectory) 794 */ 795 796 void handleUserNameAttrPatch(const std::string& userNameAttribute, 797 const std::shared_ptr<AsyncResp>& asyncResp, 798 const std::string& ldapServerElementName, 799 const std::string& ldapConfigObject) 800 { 801 crow::connections::systemBus->async_method_call( 802 [asyncResp, userNameAttribute, 803 ldapServerElementName](const boost::system::error_code ec) { 804 if (ec) 805 { 806 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 807 "username attribute"; 808 messages::internalError(asyncResp->res); 809 return; 810 } 811 auto& serverTypeJson = 812 asyncResp->res.jsonValue[ldapServerElementName]; 813 auto& searchSettingsJson = 814 serverTypeJson["LDAPService"]["SearchSettings"]; 815 searchSettingsJson["UsernameAttribute"] = userNameAttribute; 816 BMCWEB_LOG_DEBUG << "Updated the user name attr."; 817 }, 818 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 819 ldapConfigInterface, "UserNameAttribute", 820 std::variant<std::string>(userNameAttribute)); 821 } 822 /** 823 * @brief updates the LDAP group attribute and updates the 824 json response with the new value. 825 * @param groupsAttribute attribute to be updated. 826 * @param asyncResp pointer to the JSON response 827 * @param ldapServerElementName Type of LDAP 828 server(openLDAP/ActiveDirectory) 829 */ 830 831 void handleGroupNameAttrPatch(const std::string& groupsAttribute, 832 const std::shared_ptr<AsyncResp>& asyncResp, 833 const std::string& ldapServerElementName, 834 const std::string& ldapConfigObject) 835 { 836 crow::connections::systemBus->async_method_call( 837 [asyncResp, groupsAttribute, 838 ldapServerElementName](const boost::system::error_code ec) { 839 if (ec) 840 { 841 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 842 "groupname attribute"; 843 messages::internalError(asyncResp->res); 844 return; 845 } 846 auto& serverTypeJson = 847 asyncResp->res.jsonValue[ldapServerElementName]; 848 auto& searchSettingsJson = 849 serverTypeJson["LDAPService"]["SearchSettings"]; 850 searchSettingsJson["GroupsAttribute"] = groupsAttribute; 851 BMCWEB_LOG_DEBUG << "Updated the groupname attr"; 852 }, 853 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 854 ldapConfigInterface, "GroupNameAttribute", 855 std::variant<std::string>(groupsAttribute)); 856 } 857 /** 858 * @brief updates the LDAP service enable and updates the 859 json response with the new value. 860 * @param input JSON data. 861 * @param asyncResp pointer to the JSON response 862 * @param ldapServerElementName Type of LDAP 863 server(openLDAP/ActiveDirectory) 864 */ 865 866 void handleServiceEnablePatch(bool serviceEnabled, 867 const std::shared_ptr<AsyncResp>& asyncResp, 868 const std::string& ldapServerElementName, 869 const std::string& ldapConfigObject) 870 { 871 crow::connections::systemBus->async_method_call( 872 [asyncResp, serviceEnabled, 873 ldapServerElementName](const boost::system::error_code ec) { 874 if (ec) 875 { 876 BMCWEB_LOG_DEBUG 877 << "Error Occured in Updating the service enable"; 878 messages::internalError(asyncResp->res); 879 return; 880 } 881 asyncResp->res 882 .jsonValue[ldapServerElementName]["ServiceEnabled"] = 883 serviceEnabled; 884 BMCWEB_LOG_DEBUG << "Updated Service enable = " 885 << serviceEnabled; 886 }, 887 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 888 ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled)); 889 } 890 891 void handleAuthMethodsPatch(nlohmann::json& input, 892 const std::shared_ptr<AsyncResp>& asyncResp) 893 { 894 std::optional<bool> basicAuth; 895 std::optional<bool> cookie; 896 std::optional<bool> sessionToken; 897 std::optional<bool> xToken; 898 899 if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth, 900 "Cookie", cookie, "SessionToken", sessionToken, 901 "XToken", xToken)) 902 { 903 BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag"; 904 return; 905 } 906 907 // Make a copy of methods configuration 908 crow::persistent_data::AuthConfigMethods authMethodsConfig = 909 crow::persistent_data::SessionStore::getInstance() 910 .getAuthMethodsConfig(); 911 912 if (basicAuth) 913 { 914 authMethodsConfig.basic = *basicAuth; 915 } 916 917 if (cookie) 918 { 919 authMethodsConfig.cookie = *cookie; 920 } 921 922 if (sessionToken) 923 { 924 authMethodsConfig.sessionToken = *sessionToken; 925 } 926 927 if (xToken) 928 { 929 authMethodsConfig.xtoken = *xToken; 930 } 931 932 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 933 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken) 934 { 935 // Do not allow user to disable everything 936 messages::actionNotSupported(asyncResp->res, 937 "of disabling all available methods"); 938 return; 939 } 940 941 crow::persistent_data::SessionStore::getInstance() 942 .updateAuthMethodsConfig(authMethodsConfig); 943 // Save configuration immediately 944 app.template getMiddleware<crow::persistent_data::Middleware>() 945 .writeData(); 946 947 messages::success(asyncResp->res); 948 } 949 950 /** 951 * @brief Get the required values from the given JSON, validates the 952 * value and create the LDAP config object. 953 * @param input JSON data 954 * @param asyncResp pointer to the JSON response 955 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 956 */ 957 958 void handleLDAPPatch(nlohmann::json& input, 959 const std::shared_ptr<AsyncResp>& asyncResp, 960 const crow::Request& req, 961 const std::vector<std::string>& params, 962 const std::string& serverType) 963 { 964 std::string dbusObjectPath; 965 if (serverType == "ActiveDirectory") 966 { 967 dbusObjectPath = ADConfigObject; 968 } 969 else if (serverType == "LDAP") 970 { 971 dbusObjectPath = ldapConfigObject; 972 } 973 974 std::optional<nlohmann::json> authentication; 975 std::optional<nlohmann::json> ldapService; 976 std::optional<std::string> accountProviderType; 977 std::optional<std::vector<std::string>> serviceAddressList; 978 std::optional<bool> serviceEnabled; 979 std::optional<std::vector<std::string>> baseDNList; 980 std::optional<std::string> userNameAttribute; 981 std::optional<std::string> groupsAttribute; 982 std::optional<std::string> userName; 983 std::optional<std::string> password; 984 std::optional<std::vector<nlohmann::json>> remoteRoleMapData; 985 986 if (!json_util::readJson(input, asyncResp->res, "Authentication", 987 authentication, "LDAPService", ldapService, 988 "ServiceAddresses", serviceAddressList, 989 "AccountProviderType", accountProviderType, 990 "ServiceEnabled", serviceEnabled, 991 "RemoteRoleMapping", remoteRoleMapData)) 992 { 993 return; 994 } 995 996 if (authentication) 997 { 998 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 999 password); 1000 } 1001 if (ldapService) 1002 { 1003 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 1004 userNameAttribute, groupsAttribute); 1005 } 1006 if (accountProviderType) 1007 { 1008 messages::propertyNotWritable(asyncResp->res, 1009 "AccountProviderType"); 1010 } 1011 if (serviceAddressList) 1012 { 1013 if ((*serviceAddressList).size() == 0) 1014 { 1015 messages::propertyValueNotInList(asyncResp->res, "[]", 1016 "ServiceAddress"); 1017 return; 1018 } 1019 } 1020 if (baseDNList) 1021 { 1022 if ((*baseDNList).size() == 0) 1023 { 1024 messages::propertyValueNotInList(asyncResp->res, "[]", 1025 "BaseDistinguishedNames"); 1026 return; 1027 } 1028 } 1029 1030 // nothing to update, then return 1031 if (!userName && !password && !serviceAddressList && !baseDNList && 1032 !userNameAttribute && !groupsAttribute && !serviceEnabled && 1033 !remoteRoleMapData) 1034 { 1035 return; 1036 } 1037 1038 // Get the existing resource first then keep modifying 1039 // whenever any property gets updated. 1040 getLDAPConfigData(serverType, [this, asyncResp, userName, password, 1041 baseDNList, userNameAttribute, 1042 groupsAttribute, accountProviderType, 1043 serviceAddressList, serviceEnabled, 1044 dbusObjectPath, remoteRoleMapData]( 1045 bool success, LDAPConfigData confData, 1046 const std::string& serverType) { 1047 if (!success) 1048 { 1049 messages::internalError(asyncResp->res); 1050 return; 1051 } 1052 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType); 1053 if (confData.serviceEnabled) 1054 { 1055 // Disable the service first and update the rest of 1056 // the properties. 1057 handleServiceEnablePatch(false, asyncResp, serverType, 1058 dbusObjectPath); 1059 } 1060 1061 if (serviceAddressList) 1062 { 1063 handleServiceAddressPatch(*serviceAddressList, asyncResp, 1064 serverType, dbusObjectPath); 1065 } 1066 if (userName) 1067 { 1068 handleUserNamePatch(*userName, asyncResp, serverType, 1069 dbusObjectPath); 1070 } 1071 if (password) 1072 { 1073 handlePasswordPatch(*password, asyncResp, serverType, 1074 dbusObjectPath); 1075 } 1076 1077 if (baseDNList) 1078 { 1079 handleBaseDNPatch(*baseDNList, asyncResp, serverType, 1080 dbusObjectPath); 1081 } 1082 if (userNameAttribute) 1083 { 1084 handleUserNameAttrPatch(*userNameAttribute, asyncResp, 1085 serverType, dbusObjectPath); 1086 } 1087 if (groupsAttribute) 1088 { 1089 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, 1090 serverType, dbusObjectPath); 1091 } 1092 if (serviceEnabled) 1093 { 1094 // if user has given the value as true then enable 1095 // the service. if user has given false then no-op 1096 // as service is already stopped. 1097 if (*serviceEnabled) 1098 { 1099 handleServiceEnablePatch(*serviceEnabled, asyncResp, 1100 serverType, dbusObjectPath); 1101 } 1102 } 1103 else 1104 { 1105 // if user has not given the service enabled value 1106 // then revert it to the same state as it was 1107 // before. 1108 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1109 serverType, dbusObjectPath); 1110 } 1111 1112 if (remoteRoleMapData) 1113 { 1114 std::vector<nlohmann::json> remoteRoleMap = 1115 std::move(*remoteRoleMapData); 1116 1117 handleRoleMapPatch(asyncResp, confData.groupRoleList, 1118 serverType, remoteRoleMap); 1119 } 1120 }); 1121 } 1122 1123 void doGet(crow::Response& res, const crow::Request& req, 1124 const std::vector<std::string>& params) override 1125 { 1126 const crow::persistent_data::AuthConfigMethods& authMethodsConfig = 1127 crow::persistent_data::SessionStore::getInstance() 1128 .getAuthMethodsConfig(); 1129 1130 auto asyncResp = std::make_shared<AsyncResp>(res); 1131 res.jsonValue = { 1132 {"@odata.context", "/redfish/v1/" 1133 "$metadata#AccountService.AccountService"}, 1134 {"@odata.id", "/redfish/v1/AccountService"}, 1135 {"@odata.type", "#AccountService." 1136 "v1_4_0.AccountService"}, 1137 {"Id", "AccountService"}, 1138 {"Name", "Account Service"}, 1139 {"Description", "Account Service"}, 1140 {"ServiceEnabled", true}, 1141 {"MaxPasswordLength", 20}, 1142 {"Accounts", 1143 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 1144 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}, 1145 {"Oem", 1146 {{"OpenBMC", 1147 {{"@odata.type", "#OemAccountService.v1_0_0.AccountService"}, 1148 {"AuthMethods", 1149 { 1150 {"BasicAuth", authMethodsConfig.basic}, 1151 {"SessionToken", authMethodsConfig.sessionToken}, 1152 {"XToken", authMethodsConfig.xtoken}, 1153 {"Cookie", authMethodsConfig.cookie}, 1154 }}}}}}, 1155 {"LDAP", 1156 {{"Certificates", 1157 {{"@odata.id", 1158 "/redfish/v1/AccountService/LDAP/Certificates"}}}}}}; 1159 crow::connections::systemBus->async_method_call( 1160 [asyncResp]( 1161 const boost::system::error_code ec, 1162 const std::vector<std::pair< 1163 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 1164 propertiesList) { 1165 if (ec) 1166 { 1167 messages::internalError(asyncResp->res); 1168 return; 1169 } 1170 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1171 << "properties for AccountService"; 1172 for (const std::pair<std::string, 1173 std::variant<uint32_t, uint16_t, uint8_t>>& 1174 property : propertiesList) 1175 { 1176 if (property.first == "MinPasswordLength") 1177 { 1178 const uint8_t* value = 1179 std::get_if<uint8_t>(&property.second); 1180 if (value != nullptr) 1181 { 1182 asyncResp->res.jsonValue["MinPasswordLength"] = 1183 *value; 1184 } 1185 } 1186 if (property.first == "AccountUnlockTimeout") 1187 { 1188 const uint32_t* value = 1189 std::get_if<uint32_t>(&property.second); 1190 if (value != nullptr) 1191 { 1192 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1193 *value; 1194 } 1195 } 1196 if (property.first == "MaxLoginAttemptBeforeLockout") 1197 { 1198 const uint16_t* value = 1199 std::get_if<uint16_t>(&property.second); 1200 if (value != nullptr) 1201 { 1202 asyncResp->res 1203 .jsonValue["AccountLockoutThreshold"] = *value; 1204 } 1205 } 1206 } 1207 }, 1208 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1209 "org.freedesktop.DBus.Properties", "GetAll", 1210 "xyz.openbmc_project.User.AccountPolicy"); 1211 1212 auto callback = [asyncResp](bool success, LDAPConfigData& confData, 1213 const std::string& ldapType) { 1214 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1215 }; 1216 1217 getLDAPConfigData("LDAP", callback); 1218 getLDAPConfigData("ActiveDirectory", callback); 1219 } 1220 1221 void doPatch(crow::Response& res, const crow::Request& req, 1222 const std::vector<std::string>& params) override 1223 { 1224 auto asyncResp = std::make_shared<AsyncResp>(res); 1225 1226 std::optional<uint32_t> unlockTimeout; 1227 std::optional<uint16_t> lockoutThreshold; 1228 std::optional<uint16_t> minPasswordLength; 1229 std::optional<uint16_t> maxPasswordLength; 1230 std::optional<nlohmann::json> ldapObject; 1231 std::optional<nlohmann::json> activeDirectoryObject; 1232 std::optional<nlohmann::json> oemObject; 1233 1234 if (!json_util::readJson( 1235 req, res, "AccountLockoutDuration", unlockTimeout, 1236 "AccountLockoutThreshold", lockoutThreshold, 1237 "MaxPasswordLength", maxPasswordLength, "MinPasswordLength", 1238 minPasswordLength, "LDAP", ldapObject, "ActiveDirectory", 1239 activeDirectoryObject, "Oem", oemObject)) 1240 { 1241 return; 1242 } 1243 1244 if (minPasswordLength) 1245 { 1246 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 1247 } 1248 1249 if (maxPasswordLength) 1250 { 1251 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1252 } 1253 1254 if (ldapObject) 1255 { 1256 handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP"); 1257 } 1258 1259 if (std::optional<nlohmann::json> oemOpenBMCObject; 1260 oemObject && 1261 json_util::readJson(*oemObject, res, "OpenBMC", oemOpenBMCObject)) 1262 { 1263 if (std::optional<nlohmann::json> authMethodsObject; 1264 oemOpenBMCObject && 1265 json_util::readJson(*oemOpenBMCObject, res, "AuthMethods", 1266 authMethodsObject)) 1267 { 1268 if (authMethodsObject) 1269 { 1270 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1271 } 1272 } 1273 } 1274 1275 if (activeDirectoryObject) 1276 { 1277 handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params, 1278 "ActiveDirectory"); 1279 } 1280 1281 if (unlockTimeout) 1282 { 1283 crow::connections::systemBus->async_method_call( 1284 [asyncResp](const boost::system::error_code ec) { 1285 if (ec) 1286 { 1287 messages::internalError(asyncResp->res); 1288 return; 1289 } 1290 messages::success(asyncResp->res); 1291 }, 1292 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1293 "org.freedesktop.DBus.Properties", "Set", 1294 "xyz.openbmc_project.User.AccountPolicy", 1295 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 1296 } 1297 if (lockoutThreshold) 1298 { 1299 crow::connections::systemBus->async_method_call( 1300 [asyncResp](const boost::system::error_code ec) { 1301 if (ec) 1302 { 1303 messages::internalError(asyncResp->res); 1304 return; 1305 } 1306 messages::success(asyncResp->res); 1307 }, 1308 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1309 "org.freedesktop.DBus.Properties", "Set", 1310 "xyz.openbmc_project.User.AccountPolicy", 1311 "MaxLoginAttemptBeforeLockout", 1312 std::variant<uint16_t>(*lockoutThreshold)); 1313 } 1314 } 1315 1316 CrowApp& app; 1317 }; 1318 1319 class AccountsCollection : public Node 1320 { 1321 public: 1322 AccountsCollection(CrowApp& app) : 1323 Node(app, "/redfish/v1/AccountService/Accounts/") 1324 { 1325 entityPrivileges = { 1326 {boost::beast::http::verb::get, 1327 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 1328 {boost::beast::http::verb::head, {{"Login"}}}, 1329 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1330 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1331 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1332 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1333 } 1334 1335 private: 1336 void doGet(crow::Response& res, const crow::Request& req, 1337 const std::vector<std::string>& params) override 1338 { 1339 auto asyncResp = std::make_shared<AsyncResp>(res); 1340 res.jsonValue = {{"@odata.context", 1341 "/redfish/v1/" 1342 "$metadata#ManagerAccountCollection." 1343 "ManagerAccountCollection"}, 1344 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 1345 {"@odata.type", "#ManagerAccountCollection." 1346 "ManagerAccountCollection"}, 1347 {"Name", "Accounts Collection"}, 1348 {"Description", "BMC User Accounts"}}; 1349 1350 crow::connections::systemBus->async_method_call( 1351 [asyncResp](const boost::system::error_code ec, 1352 const ManagedObjectType& users) { 1353 if (ec) 1354 { 1355 messages::internalError(asyncResp->res); 1356 return; 1357 } 1358 1359 nlohmann::json& memberArray = 1360 asyncResp->res.jsonValue["Members"]; 1361 memberArray = nlohmann::json::array(); 1362 1363 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 1364 for (auto& user : users) 1365 { 1366 const std::string& path = 1367 static_cast<const std::string&>(user.first); 1368 std::size_t lastIndex = path.rfind("/"); 1369 if (lastIndex == std::string::npos) 1370 { 1371 lastIndex = 0; 1372 } 1373 else 1374 { 1375 lastIndex += 1; 1376 } 1377 memberArray.push_back( 1378 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 1379 path.substr(lastIndex)}}); 1380 } 1381 }, 1382 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1383 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1384 } 1385 void doPost(crow::Response& res, const crow::Request& req, 1386 const std::vector<std::string>& params) override 1387 { 1388 auto asyncResp = std::make_shared<AsyncResp>(res); 1389 1390 std::string username; 1391 std::string password; 1392 std::optional<std::string> roleId("User"); 1393 std::optional<bool> enabled = true; 1394 if (!json_util::readJson(req, res, "UserName", username, "Password", 1395 password, "RoleId", roleId, "Enabled", 1396 enabled)) 1397 { 1398 return; 1399 } 1400 1401 std::string priv = getPrivilegeFromRoleId(*roleId); 1402 if (priv.empty()) 1403 { 1404 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1405 return; 1406 } 1407 roleId = priv; 1408 1409 // Reading AllGroups property 1410 crow::connections::systemBus->async_method_call( 1411 [asyncResp, username, password{std::move(password)}, roleId, 1412 enabled](const boost::system::error_code ec, 1413 const std::variant<std::vector<std::string>>& allGroups) { 1414 if (ec) 1415 { 1416 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1417 messages::internalError(asyncResp->res); 1418 return; 1419 } 1420 1421 const std::vector<std::string>* allGroupsList = 1422 std::get_if<std::vector<std::string>>(&allGroups); 1423 1424 if (allGroupsList == nullptr || allGroupsList->empty()) 1425 { 1426 messages::internalError(asyncResp->res); 1427 return; 1428 } 1429 1430 crow::connections::systemBus->async_method_call( 1431 [asyncResp, username, password{std::move(password)}]( 1432 const boost::system::error_code ec) { 1433 if (ec) 1434 { 1435 messages::resourceAlreadyExists( 1436 asyncResp->res, 1437 "#ManagerAccount.v1_0_3.ManagerAccount", 1438 "UserName", username); 1439 return; 1440 } 1441 1442 if (pamUpdatePassword(username, password) != 1443 PAM_SUCCESS) 1444 { 1445 // At this point we have a user that's been created, 1446 // but the password set failed.Something is wrong, 1447 // so delete the user that we've already created 1448 crow::connections::systemBus->async_method_call( 1449 [asyncResp]( 1450 const boost::system::error_code ec) { 1451 if (ec) 1452 { 1453 messages::internalError(asyncResp->res); 1454 return; 1455 } 1456 1457 messages::invalidObject(asyncResp->res, 1458 "Password"); 1459 }, 1460 "xyz.openbmc_project.User.Manager", 1461 "/xyz/openbmc_project/user/" + username, 1462 "xyz.openbmc_project.Object.Delete", "Delete"); 1463 1464 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1465 return; 1466 } 1467 1468 messages::created(asyncResp->res); 1469 asyncResp->res.addHeader( 1470 "Location", 1471 "/redfish/v1/AccountService/Accounts/" + username); 1472 }, 1473 "xyz.openbmc_project.User.Manager", 1474 "/xyz/openbmc_project/user", 1475 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1476 *allGroupsList, *roleId, *enabled); 1477 }, 1478 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1479 "org.freedesktop.DBus.Properties", "Get", 1480 "xyz.openbmc_project.User.Manager", "AllGroups"); 1481 } 1482 }; 1483 1484 class ManagerAccount : public Node 1485 { 1486 public: 1487 ManagerAccount(CrowApp& app) : 1488 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1489 { 1490 entityPrivileges = { 1491 {boost::beast::http::verb::get, 1492 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1493 {boost::beast::http::verb::head, {{"Login"}}}, 1494 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1495 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1496 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1497 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1498 } 1499 1500 private: 1501 void doGet(crow::Response& res, const crow::Request& req, 1502 const std::vector<std::string>& params) override 1503 { 1504 1505 auto asyncResp = std::make_shared<AsyncResp>(res); 1506 1507 if (params.size() != 1) 1508 { 1509 messages::internalError(asyncResp->res); 1510 return; 1511 } 1512 1513 crow::connections::systemBus->async_method_call( 1514 [asyncResp, accountName{std::string(params[0])}]( 1515 const boost::system::error_code ec, 1516 const ManagedObjectType& users) { 1517 if (ec) 1518 { 1519 messages::internalError(asyncResp->res); 1520 return; 1521 } 1522 auto userIt = users.begin(); 1523 1524 for (; userIt != users.end(); userIt++) 1525 { 1526 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1527 { 1528 break; 1529 } 1530 } 1531 if (userIt == users.end()) 1532 { 1533 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1534 accountName); 1535 return; 1536 } 1537 1538 asyncResp->res.jsonValue = { 1539 {"@odata.context", 1540 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 1541 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 1542 {"Name", "User Account"}, 1543 {"Description", "User Account"}, 1544 {"Password", nullptr}}; 1545 1546 for (const auto& interface : userIt->second) 1547 { 1548 if (interface.first == 1549 "xyz.openbmc_project.User.Attributes") 1550 { 1551 for (const auto& property : interface.second) 1552 { 1553 if (property.first == "UserEnabled") 1554 { 1555 const bool* userEnabled = 1556 std::get_if<bool>(&property.second); 1557 if (userEnabled == nullptr) 1558 { 1559 BMCWEB_LOG_ERROR 1560 << "UserEnabled wasn't a bool"; 1561 messages::internalError(asyncResp->res); 1562 return; 1563 } 1564 asyncResp->res.jsonValue["Enabled"] = 1565 *userEnabled; 1566 } 1567 else if (property.first == 1568 "UserLockedForFailedAttempt") 1569 { 1570 const bool* userLocked = 1571 std::get_if<bool>(&property.second); 1572 if (userLocked == nullptr) 1573 { 1574 BMCWEB_LOG_ERROR << "UserLockedForF" 1575 "ailedAttempt " 1576 "wasn't a bool"; 1577 messages::internalError(asyncResp->res); 1578 return; 1579 } 1580 asyncResp->res.jsonValue["Locked"] = 1581 *userLocked; 1582 asyncResp->res.jsonValue 1583 ["Locked@Redfish.AllowableValues"] = { 1584 "false"}; 1585 } 1586 else if (property.first == "UserPrivilege") 1587 { 1588 const std::string* userPrivPtr = 1589 std::get_if<std::string>(&property.second); 1590 if (userPrivPtr == nullptr) 1591 { 1592 BMCWEB_LOG_ERROR 1593 << "UserPrivilege wasn't a " 1594 "string"; 1595 messages::internalError(asyncResp->res); 1596 return; 1597 } 1598 std::string role = 1599 getRoleIdFromPrivilege(*userPrivPtr); 1600 if (role.empty()) 1601 { 1602 BMCWEB_LOG_ERROR << "Invalid user role"; 1603 messages::internalError(asyncResp->res); 1604 return; 1605 } 1606 asyncResp->res.jsonValue["RoleId"] = role; 1607 1608 asyncResp->res.jsonValue["Links"]["Role"] = { 1609 {"@odata.id", "/redfish/v1/AccountService/" 1610 "Roles/" + 1611 role}}; 1612 } 1613 } 1614 } 1615 } 1616 1617 asyncResp->res.jsonValue["@odata.id"] = 1618 "/redfish/v1/AccountService/Accounts/" + accountName; 1619 asyncResp->res.jsonValue["Id"] = accountName; 1620 asyncResp->res.jsonValue["UserName"] = accountName; 1621 }, 1622 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1623 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1624 } 1625 1626 void doPatch(crow::Response& res, const crow::Request& req, 1627 const std::vector<std::string>& params) override 1628 { 1629 auto asyncResp = std::make_shared<AsyncResp>(res); 1630 if (params.size() != 1) 1631 { 1632 messages::internalError(asyncResp->res); 1633 return; 1634 } 1635 1636 std::optional<std::string> newUserName; 1637 std::optional<std::string> password; 1638 std::optional<bool> enabled; 1639 std::optional<std::string> roleId; 1640 std::optional<bool> locked; 1641 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1642 password, "RoleId", roleId, "Enabled", enabled, 1643 "Locked", locked)) 1644 { 1645 return; 1646 } 1647 1648 const std::string& username = params[0]; 1649 1650 // if user name is not provided in the patch method or if it 1651 // matches the user name in the URI, then we are treating it as updating 1652 // user properties other then username. If username provided doesn't 1653 // match the URI, then we are treating this as user rename request. 1654 if (!newUserName || (newUserName.value() == username)) 1655 { 1656 updateUserProperties(asyncResp, username, password, enabled, roleId, 1657 locked); 1658 return; 1659 } 1660 else 1661 { 1662 crow::connections::systemBus->async_method_call( 1663 [this, asyncResp, username, password(std::move(password)), 1664 roleId(std::move(roleId)), enabled(std::move(enabled)), 1665 newUser{std::string(*newUserName)}, 1666 locked(std::move(locked))](const boost::system::error_code ec, 1667 sdbusplus::message::message& m) { 1668 if (ec) 1669 { 1670 userErrorMessageHandler(m.get_error(), asyncResp, 1671 newUser, username); 1672 return; 1673 } 1674 1675 updateUserProperties(asyncResp, newUser, password, enabled, 1676 roleId, locked); 1677 }, 1678 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1679 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1680 *newUserName); 1681 } 1682 } 1683 1684 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1685 const std::string& username, 1686 std::optional<std::string> password, 1687 std::optional<bool> enabled, 1688 std::optional<std::string> roleId, 1689 std::optional<bool> locked) 1690 { 1691 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1692 dbus::utility::escapePathForDbus(dbusObjectPath); 1693 1694 dbus::utility::checkDbusPathExists( 1695 dbusObjectPath, 1696 [dbusObjectPath(std::move(dbusObjectPath)), username, 1697 password(std::move(password)), roleId(std::move(roleId)), 1698 enabled(std::move(enabled)), locked(std::move(locked)), 1699 asyncResp{std::move(asyncResp)}](int rc) { 1700 if (!rc) 1701 { 1702 messages::resourceNotFound( 1703 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1704 username); 1705 return; 1706 } 1707 1708 if (password) 1709 { 1710 int retval = pamUpdatePassword(username, *password); 1711 1712 if (retval == PAM_USER_UNKNOWN) 1713 { 1714 messages::resourceNotFound( 1715 asyncResp->res, 1716 "#ManagerAccount.v1_0_3.ManagerAccount", username); 1717 } 1718 else if (retval == PAM_AUTHTOK_ERR) 1719 { 1720 // If password is invalid 1721 messages::propertyValueFormatError( 1722 asyncResp->res, *password, "Password"); 1723 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1724 } 1725 else if (retval != PAM_SUCCESS) 1726 { 1727 messages::internalError(asyncResp->res); 1728 return; 1729 } 1730 } 1731 1732 if (enabled) 1733 { 1734 crow::connections::systemBus->async_method_call( 1735 [asyncResp](const boost::system::error_code ec) { 1736 if (ec) 1737 { 1738 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1739 << ec; 1740 messages::internalError(asyncResp->res); 1741 return; 1742 } 1743 messages::success(asyncResp->res); 1744 return; 1745 }, 1746 "xyz.openbmc_project.User.Manager", 1747 dbusObjectPath.c_str(), 1748 "org.freedesktop.DBus.Properties", "Set", 1749 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1750 std::variant<bool>{*enabled}); 1751 } 1752 1753 if (roleId) 1754 { 1755 std::string priv = getPrivilegeFromRoleId(*roleId); 1756 if (priv.empty()) 1757 { 1758 messages::propertyValueNotInList(asyncResp->res, 1759 *roleId, "RoleId"); 1760 return; 1761 } 1762 1763 crow::connections::systemBus->async_method_call( 1764 [asyncResp](const boost::system::error_code ec) { 1765 if (ec) 1766 { 1767 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1768 << ec; 1769 messages::internalError(asyncResp->res); 1770 return; 1771 } 1772 messages::success(asyncResp->res); 1773 }, 1774 "xyz.openbmc_project.User.Manager", 1775 dbusObjectPath.c_str(), 1776 "org.freedesktop.DBus.Properties", "Set", 1777 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1778 std::variant<std::string>{priv}); 1779 } 1780 1781 if (locked) 1782 { 1783 // admin can unlock the account which is locked by 1784 // successive authentication failures but admin should 1785 // not be allowed to lock an account. 1786 if (*locked) 1787 { 1788 messages::propertyValueNotInList(asyncResp->res, "true", 1789 "Locked"); 1790 return; 1791 } 1792 1793 crow::connections::systemBus->async_method_call( 1794 [asyncResp](const boost::system::error_code ec) { 1795 if (ec) 1796 { 1797 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1798 << ec; 1799 messages::internalError(asyncResp->res); 1800 return; 1801 } 1802 messages::success(asyncResp->res); 1803 return; 1804 }, 1805 "xyz.openbmc_project.User.Manager", 1806 dbusObjectPath.c_str(), 1807 "org.freedesktop.DBus.Properties", "Set", 1808 "xyz.openbmc_project.User.Attributes", 1809 "UserLockedForFailedAttempt", 1810 sdbusplus::message::variant<bool>{*locked}); 1811 } 1812 }); 1813 } 1814 1815 void doDelete(crow::Response& res, const crow::Request& req, 1816 const std::vector<std::string>& params) override 1817 { 1818 auto asyncResp = std::make_shared<AsyncResp>(res); 1819 1820 if (params.size() != 1) 1821 { 1822 messages::internalError(asyncResp->res); 1823 return; 1824 } 1825 1826 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1827 1828 crow::connections::systemBus->async_method_call( 1829 [asyncResp, username{std::move(params[0])}]( 1830 const boost::system::error_code ec) { 1831 if (ec) 1832 { 1833 messages::resourceNotFound( 1834 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1835 username); 1836 return; 1837 } 1838 1839 messages::accountRemoved(asyncResp->res); 1840 }, 1841 "xyz.openbmc_project.User.Manager", userPath, 1842 "xyz.openbmc_project.Object.Delete", "Delete"); 1843 } 1844 }; 1845 1846 } // namespace redfish 1847