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