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