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