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, 564 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 565 {boost::beast::http::verb::head, {{"Login"}}}, 566 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 567 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 568 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 569 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 570 } 571 572 private: 573 /** 574 * @brief parses the authentication section under the LDAP 575 * @param input JSON data 576 * @param asyncResp pointer to the JSON response 577 * @param userName userName to be filled from the given JSON. 578 * @param password password to be filled from the given JSON. 579 */ 580 void 581 parseLDAPAuthenticationJson(nlohmann::json input, 582 const std::shared_ptr<AsyncResp>& asyncResp, 583 std::optional<std::string>& username, 584 std::optional<std::string>& password) 585 { 586 std::optional<std::string> authType; 587 588 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 589 authType, "Username", username, "Password", 590 password)) 591 { 592 return; 593 } 594 if (!authType) 595 { 596 return; 597 } 598 if (*authType != "UsernameAndPassword") 599 { 600 messages::propertyValueNotInList(asyncResp->res, *authType, 601 "AuthenticationType"); 602 return; 603 } 604 } 605 /** 606 * @brief parses the LDAPService section under the LDAP 607 * @param input JSON data 608 * @param asyncResp pointer to the JSON response 609 * @param baseDNList baseDN to be filled from the given JSON. 610 * @param userNameAttribute userName to be filled from the given JSON. 611 * @param groupaAttribute password to be filled from the given JSON. 612 */ 613 614 void parseLDAPServiceJson( 615 nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp, 616 std::optional<std::vector<std::string>>& baseDNList, 617 std::optional<std::string>& userNameAttribute, 618 std::optional<std::string>& groupsAttribute) 619 { 620 std::optional<nlohmann::json> searchSettings; 621 622 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 623 searchSettings)) 624 { 625 return; 626 } 627 if (!searchSettings) 628 { 629 return; 630 } 631 if (!json_util::readJson(*searchSettings, asyncResp->res, 632 "BaseDistinguishedNames", baseDNList, 633 "UsernameAttribute", userNameAttribute, 634 "GroupsAttribute", groupsAttribute)) 635 { 636 return; 637 } 638 } 639 /** 640 * @brief updates the LDAP server address and updates the 641 json response with the new value. 642 * @param serviceAddressList address to be updated. 643 * @param asyncResp pointer to the JSON response 644 * @param ldapServerElementName Type of LDAP 645 server(openLDAP/ActiveDirectory) 646 */ 647 648 void handleServiceAddressPatch( 649 const std::vector<std::string>& serviceAddressList, 650 const std::shared_ptr<AsyncResp>& asyncResp, 651 const std::string& ldapServerElementName, 652 const std::string& ldapConfigObject) 653 { 654 crow::connections::systemBus->async_method_call( 655 [asyncResp, ldapServerElementName, 656 serviceAddressList](const boost::system::error_code ec) { 657 if (ec) 658 { 659 BMCWEB_LOG_DEBUG 660 << "Error Occured in updating the service address"; 661 messages::internalError(asyncResp->res); 662 return; 663 } 664 std::vector<std::string> modifiedserviceAddressList = { 665 serviceAddressList.front()}; 666 asyncResp->res 667 .jsonValue[ldapServerElementName]["ServiceAddresses"] = 668 modifiedserviceAddressList; 669 if ((serviceAddressList).size() > 1) 670 { 671 messages::propertyValueModified(asyncResp->res, 672 "ServiceAddresses", 673 serviceAddressList.front()); 674 } 675 BMCWEB_LOG_DEBUG << "Updated the service address"; 676 }, 677 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 678 ldapConfigInterface, "LDAPServerURI", 679 std::variant<std::string>(serviceAddressList.front())); 680 } 681 /** 682 * @brief updates the LDAP Bind DN and updates the 683 json response with the new value. 684 * @param username name of the user which needs to be updated. 685 * @param asyncResp pointer to the JSON response 686 * @param ldapServerElementName Type of LDAP 687 server(openLDAP/ActiveDirectory) 688 */ 689 690 void handleUserNamePatch(const std::string& username, 691 const std::shared_ptr<AsyncResp>& asyncResp, 692 const std::string& ldapServerElementName, 693 const std::string& ldapConfigObject) 694 { 695 crow::connections::systemBus->async_method_call( 696 [asyncResp, username, 697 ldapServerElementName](const boost::system::error_code ec) { 698 if (ec) 699 { 700 BMCWEB_LOG_DEBUG 701 << "Error occured in updating the username"; 702 messages::internalError(asyncResp->res); 703 return; 704 } 705 asyncResp->res.jsonValue[ldapServerElementName] 706 ["Authentication"]["Username"] = 707 username; 708 BMCWEB_LOG_DEBUG << "Updated the username"; 709 }, 710 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 711 ldapConfigInterface, "LDAPBindDN", 712 std::variant<std::string>(username)); 713 } 714 715 /** 716 * @brief updates the LDAP password 717 * @param password : ldap password which needs to be updated. 718 * @param asyncResp pointer to the JSON response 719 * @param ldapServerElementName Type of LDAP 720 * server(openLDAP/ActiveDirectory) 721 */ 722 723 void handlePasswordPatch(const std::string& password, 724 const std::shared_ptr<AsyncResp>& asyncResp, 725 const std::string& ldapServerElementName, 726 const std::string& ldapConfigObject) 727 { 728 crow::connections::systemBus->async_method_call( 729 [asyncResp, password, 730 ldapServerElementName](const boost::system::error_code ec) { 731 if (ec) 732 { 733 BMCWEB_LOG_DEBUG 734 << "Error occured in updating the password"; 735 messages::internalError(asyncResp->res); 736 return; 737 } 738 asyncResp->res.jsonValue[ldapServerElementName] 739 ["Authentication"]["Password"] = ""; 740 BMCWEB_LOG_DEBUG << "Updated the password"; 741 }, 742 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 743 ldapConfigInterface, "LDAPBindDNPassword", 744 std::variant<std::string>(password)); 745 } 746 747 /** 748 * @brief updates the LDAP BaseDN and updates the 749 json response with the new value. 750 * @param baseDNList baseDN list which needs to be updated. 751 * @param asyncResp pointer to the JSON response 752 * @param ldapServerElementName Type of LDAP 753 server(openLDAP/ActiveDirectory) 754 */ 755 756 void handleBaseDNPatch(const std::vector<std::string>& baseDNList, 757 const std::shared_ptr<AsyncResp>& asyncResp, 758 const std::string& ldapServerElementName, 759 const std::string& ldapConfigObject) 760 { 761 crow::connections::systemBus->async_method_call( 762 [asyncResp, baseDNList, 763 ldapServerElementName](const boost::system::error_code ec) { 764 if (ec) 765 { 766 BMCWEB_LOG_DEBUG << "Error Occured in Updating the base DN"; 767 messages::internalError(asyncResp->res); 768 return; 769 } 770 auto& serverTypeJson = 771 asyncResp->res.jsonValue[ldapServerElementName]; 772 auto& searchSettingsJson = 773 serverTypeJson["LDAPService"]["SearchSettings"]; 774 std::vector<std::string> modifiedBaseDNList = { 775 baseDNList.front()}; 776 searchSettingsJson["BaseDistinguishedNames"] = 777 modifiedBaseDNList; 778 if (baseDNList.size() > 1) 779 { 780 messages::propertyValueModified(asyncResp->res, 781 "BaseDistinguishedNames", 782 baseDNList.front()); 783 } 784 BMCWEB_LOG_DEBUG << "Updated the base DN"; 785 }, 786 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 787 ldapConfigInterface, "LDAPBaseDN", 788 std::variant<std::string>(baseDNList.front())); 789 } 790 /** 791 * @brief updates the LDAP user name attribute and updates the 792 json response with the new value. 793 * @param userNameAttribute attribute to be updated. 794 * @param asyncResp pointer to the JSON response 795 * @param ldapServerElementName Type of LDAP 796 server(openLDAP/ActiveDirectory) 797 */ 798 799 void handleUserNameAttrPatch(const std::string& userNameAttribute, 800 const std::shared_ptr<AsyncResp>& asyncResp, 801 const std::string& ldapServerElementName, 802 const std::string& ldapConfigObject) 803 { 804 crow::connections::systemBus->async_method_call( 805 [asyncResp, userNameAttribute, 806 ldapServerElementName](const boost::system::error_code ec) { 807 if (ec) 808 { 809 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 810 "username attribute"; 811 messages::internalError(asyncResp->res); 812 return; 813 } 814 auto& serverTypeJson = 815 asyncResp->res.jsonValue[ldapServerElementName]; 816 auto& searchSettingsJson = 817 serverTypeJson["LDAPService"]["SearchSettings"]; 818 searchSettingsJson["UsernameAttribute"] = userNameAttribute; 819 BMCWEB_LOG_DEBUG << "Updated the user name attr."; 820 }, 821 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 822 ldapConfigInterface, "UserNameAttribute", 823 std::variant<std::string>(userNameAttribute)); 824 } 825 /** 826 * @brief updates the LDAP group attribute and updates the 827 json response with the new value. 828 * @param groupsAttribute attribute to be updated. 829 * @param asyncResp pointer to the JSON response 830 * @param ldapServerElementName Type of LDAP 831 server(openLDAP/ActiveDirectory) 832 */ 833 834 void handleGroupNameAttrPatch(const std::string& groupsAttribute, 835 const std::shared_ptr<AsyncResp>& asyncResp, 836 const std::string& ldapServerElementName, 837 const std::string& ldapConfigObject) 838 { 839 crow::connections::systemBus->async_method_call( 840 [asyncResp, groupsAttribute, 841 ldapServerElementName](const boost::system::error_code ec) { 842 if (ec) 843 { 844 BMCWEB_LOG_DEBUG << "Error Occured in Updating the " 845 "groupname attribute"; 846 messages::internalError(asyncResp->res); 847 return; 848 } 849 auto& serverTypeJson = 850 asyncResp->res.jsonValue[ldapServerElementName]; 851 auto& searchSettingsJson = 852 serverTypeJson["LDAPService"]["SearchSettings"]; 853 searchSettingsJson["GroupsAttribute"] = groupsAttribute; 854 BMCWEB_LOG_DEBUG << "Updated the groupname attr"; 855 }, 856 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 857 ldapConfigInterface, "GroupNameAttribute", 858 std::variant<std::string>(groupsAttribute)); 859 } 860 /** 861 * @brief updates the LDAP service enable and updates the 862 json response with the new value. 863 * @param input JSON data. 864 * @param asyncResp pointer to the JSON response 865 * @param ldapServerElementName Type of LDAP 866 server(openLDAP/ActiveDirectory) 867 */ 868 869 void handleServiceEnablePatch(bool serviceEnabled, 870 const std::shared_ptr<AsyncResp>& asyncResp, 871 const std::string& ldapServerElementName, 872 const std::string& ldapConfigObject) 873 { 874 crow::connections::systemBus->async_method_call( 875 [asyncResp, serviceEnabled, 876 ldapServerElementName](const boost::system::error_code ec) { 877 if (ec) 878 { 879 BMCWEB_LOG_DEBUG 880 << "Error Occured in Updating the service enable"; 881 messages::internalError(asyncResp->res); 882 return; 883 } 884 asyncResp->res 885 .jsonValue[ldapServerElementName]["ServiceEnabled"] = 886 serviceEnabled; 887 BMCWEB_LOG_DEBUG << "Updated Service enable = " 888 << serviceEnabled; 889 }, 890 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 891 ldapEnableInterface, "Enabled", std::variant<bool>(serviceEnabled)); 892 } 893 894 void handleAuthMethodsPatch(nlohmann::json& input, 895 const std::shared_ptr<AsyncResp>& asyncResp) 896 { 897 std::optional<bool> basicAuth; 898 std::optional<bool> cookie; 899 std::optional<bool> sessionToken; 900 std::optional<bool> xToken; 901 std::optional<bool> tls; 902 903 if (!json_util::readJson(input, asyncResp->res, "BasicAuth", basicAuth, 904 "Cookie", cookie, "SessionToken", sessionToken, 905 "XToken", xToken, "TLS", tls)) 906 { 907 BMCWEB_LOG_ERROR << "Cannot read values from AuthMethod tag"; 908 return; 909 } 910 911 // Make a copy of methods configuration 912 crow::persistent_data::AuthConfigMethods authMethodsConfig = 913 crow::persistent_data::SessionStore::getInstance() 914 .getAuthMethodsConfig(); 915 916 if (basicAuth) 917 { 918 authMethodsConfig.basic = *basicAuth; 919 } 920 921 if (cookie) 922 { 923 authMethodsConfig.cookie = *cookie; 924 } 925 926 if (sessionToken) 927 { 928 authMethodsConfig.sessionToken = *sessionToken; 929 } 930 931 if (xToken) 932 { 933 authMethodsConfig.xtoken = *xToken; 934 } 935 936 if (tls) 937 { 938 authMethodsConfig.tls = *tls; 939 } 940 941 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 942 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken && 943 !authMethodsConfig.tls) 944 { 945 // Do not allow user to disable everything 946 messages::actionNotSupported(asyncResp->res, 947 "of disabling all available methods"); 948 return; 949 } 950 951 crow::persistent_data::SessionStore::getInstance() 952 .updateAuthMethodsConfig(authMethodsConfig); 953 // Save configuration immediately 954 app.template getMiddleware<crow::persistent_data::Middleware>() 955 .writeData(); 956 957 messages::success(asyncResp->res); 958 } 959 960 /** 961 * @brief Get the required values from the given JSON, validates the 962 * value and create the LDAP config object. 963 * @param input JSON data 964 * @param asyncResp pointer to the JSON response 965 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 966 */ 967 968 void handleLDAPPatch(nlohmann::json& input, 969 const std::shared_ptr<AsyncResp>& asyncResp, 970 const crow::Request& req, 971 const std::vector<std::string>& params, 972 const std::string& serverType) 973 { 974 std::string dbusObjectPath; 975 if (serverType == "ActiveDirectory") 976 { 977 dbusObjectPath = ADConfigObject; 978 } 979 else if (serverType == "LDAP") 980 { 981 dbusObjectPath = ldapConfigObject; 982 } 983 984 std::optional<nlohmann::json> authentication; 985 std::optional<nlohmann::json> ldapService; 986 std::optional<std::string> accountProviderType; 987 std::optional<std::vector<std::string>> serviceAddressList; 988 std::optional<bool> serviceEnabled; 989 std::optional<std::vector<std::string>> baseDNList; 990 std::optional<std::string> userNameAttribute; 991 std::optional<std::string> groupsAttribute; 992 std::optional<std::string> userName; 993 std::optional<std::string> password; 994 std::optional<std::vector<nlohmann::json>> remoteRoleMapData; 995 996 if (!json_util::readJson(input, asyncResp->res, "Authentication", 997 authentication, "LDAPService", ldapService, 998 "ServiceAddresses", serviceAddressList, 999 "AccountProviderType", accountProviderType, 1000 "ServiceEnabled", serviceEnabled, 1001 "RemoteRoleMapping", remoteRoleMapData)) 1002 { 1003 return; 1004 } 1005 1006 if (authentication) 1007 { 1008 parseLDAPAuthenticationJson(*authentication, asyncResp, userName, 1009 password); 1010 } 1011 if (ldapService) 1012 { 1013 parseLDAPServiceJson(*ldapService, asyncResp, baseDNList, 1014 userNameAttribute, groupsAttribute); 1015 } 1016 if (accountProviderType) 1017 { 1018 messages::propertyNotWritable(asyncResp->res, 1019 "AccountProviderType"); 1020 } 1021 if (serviceAddressList) 1022 { 1023 if ((*serviceAddressList).size() == 0) 1024 { 1025 messages::propertyValueNotInList(asyncResp->res, "[]", 1026 "ServiceAddress"); 1027 return; 1028 } 1029 } 1030 if (baseDNList) 1031 { 1032 if ((*baseDNList).size() == 0) 1033 { 1034 messages::propertyValueNotInList(asyncResp->res, "[]", 1035 "BaseDistinguishedNames"); 1036 return; 1037 } 1038 } 1039 1040 // nothing to update, then return 1041 if (!userName && !password && !serviceAddressList && !baseDNList && 1042 !userNameAttribute && !groupsAttribute && !serviceEnabled && 1043 !remoteRoleMapData) 1044 { 1045 return; 1046 } 1047 1048 // Get the existing resource first then keep modifying 1049 // whenever any property gets updated. 1050 getLDAPConfigData(serverType, [this, asyncResp, userName, password, 1051 baseDNList, userNameAttribute, 1052 groupsAttribute, accountProviderType, 1053 serviceAddressList, serviceEnabled, 1054 dbusObjectPath, remoteRoleMapData]( 1055 bool success, LDAPConfigData confData, 1056 const std::string& serverType) { 1057 if (!success) 1058 { 1059 messages::internalError(asyncResp->res); 1060 return; 1061 } 1062 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverType); 1063 if (confData.serviceEnabled) 1064 { 1065 // Disable the service first and update the rest of 1066 // the properties. 1067 handleServiceEnablePatch(false, asyncResp, serverType, 1068 dbusObjectPath); 1069 } 1070 1071 if (serviceAddressList) 1072 { 1073 handleServiceAddressPatch(*serviceAddressList, asyncResp, 1074 serverType, dbusObjectPath); 1075 } 1076 if (userName) 1077 { 1078 handleUserNamePatch(*userName, asyncResp, serverType, 1079 dbusObjectPath); 1080 } 1081 if (password) 1082 { 1083 handlePasswordPatch(*password, asyncResp, serverType, 1084 dbusObjectPath); 1085 } 1086 1087 if (baseDNList) 1088 { 1089 handleBaseDNPatch(*baseDNList, asyncResp, serverType, 1090 dbusObjectPath); 1091 } 1092 if (userNameAttribute) 1093 { 1094 handleUserNameAttrPatch(*userNameAttribute, asyncResp, 1095 serverType, dbusObjectPath); 1096 } 1097 if (groupsAttribute) 1098 { 1099 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, 1100 serverType, dbusObjectPath); 1101 } 1102 if (serviceEnabled) 1103 { 1104 // if user has given the value as true then enable 1105 // the service. if user has given false then no-op 1106 // as service is already stopped. 1107 if (*serviceEnabled) 1108 { 1109 handleServiceEnablePatch(*serviceEnabled, asyncResp, 1110 serverType, dbusObjectPath); 1111 } 1112 } 1113 else 1114 { 1115 // if user has not given the service enabled value 1116 // then revert it to the same state as it was 1117 // before. 1118 handleServiceEnablePatch(confData.serviceEnabled, asyncResp, 1119 serverType, dbusObjectPath); 1120 } 1121 1122 if (remoteRoleMapData) 1123 { 1124 std::vector<nlohmann::json> remoteRoleMap = 1125 std::move(*remoteRoleMapData); 1126 1127 handleRoleMapPatch(asyncResp, confData.groupRoleList, 1128 serverType, remoteRoleMap); 1129 } 1130 }); 1131 } 1132 1133 void doGet(crow::Response& res, const crow::Request& req, 1134 const std::vector<std::string>& params) override 1135 { 1136 const crow::persistent_data::AuthConfigMethods& authMethodsConfig = 1137 crow::persistent_data::SessionStore::getInstance() 1138 .getAuthMethodsConfig(); 1139 1140 auto asyncResp = std::make_shared<AsyncResp>(res); 1141 res.jsonValue = { 1142 {"@odata.context", "/redfish/v1/" 1143 "$metadata#AccountService.AccountService"}, 1144 {"@odata.id", "/redfish/v1/AccountService"}, 1145 {"@odata.type", "#AccountService." 1146 "v1_4_0.AccountService"}, 1147 {"Id", "AccountService"}, 1148 {"Name", "Account Service"}, 1149 {"Description", "Account Service"}, 1150 {"ServiceEnabled", true}, 1151 {"MaxPasswordLength", 20}, 1152 {"Accounts", 1153 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 1154 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}, 1155 {"Oem", 1156 {{"OpenBMC", 1157 {{"@odata.type", "#OemAccountService.v1_0_0.AccountService"}, 1158 {"AuthMethods", 1159 { 1160 {"BasicAuth", authMethodsConfig.basic}, 1161 {"SessionToken", authMethodsConfig.sessionToken}, 1162 {"XToken", authMethodsConfig.xtoken}, 1163 {"Cookie", authMethodsConfig.cookie}, 1164 {"TLS", authMethodsConfig.tls}, 1165 }}}}}}, 1166 {"LDAP", 1167 {{"Certificates", 1168 {{"@odata.id", 1169 "/redfish/v1/AccountService/LDAP/Certificates"}}}}}}; 1170 crow::connections::systemBus->async_method_call( 1171 [asyncResp]( 1172 const boost::system::error_code ec, 1173 const std::vector<std::pair< 1174 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 1175 propertiesList) { 1176 if (ec) 1177 { 1178 messages::internalError(asyncResp->res); 1179 return; 1180 } 1181 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1182 << "properties for AccountService"; 1183 for (const std::pair<std::string, 1184 std::variant<uint32_t, uint16_t, uint8_t>>& 1185 property : propertiesList) 1186 { 1187 if (property.first == "MinPasswordLength") 1188 { 1189 const uint8_t* value = 1190 std::get_if<uint8_t>(&property.second); 1191 if (value != nullptr) 1192 { 1193 asyncResp->res.jsonValue["MinPasswordLength"] = 1194 *value; 1195 } 1196 } 1197 if (property.first == "AccountUnlockTimeout") 1198 { 1199 const uint32_t* value = 1200 std::get_if<uint32_t>(&property.second); 1201 if (value != nullptr) 1202 { 1203 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1204 *value; 1205 } 1206 } 1207 if (property.first == "MaxLoginAttemptBeforeLockout") 1208 { 1209 const uint16_t* value = 1210 std::get_if<uint16_t>(&property.second); 1211 if (value != nullptr) 1212 { 1213 asyncResp->res 1214 .jsonValue["AccountLockoutThreshold"] = *value; 1215 } 1216 } 1217 } 1218 }, 1219 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1220 "org.freedesktop.DBus.Properties", "GetAll", 1221 "xyz.openbmc_project.User.AccountPolicy"); 1222 1223 auto callback = [asyncResp](bool success, LDAPConfigData& confData, 1224 const std::string& ldapType) { 1225 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1226 }; 1227 1228 getLDAPConfigData("LDAP", callback); 1229 getLDAPConfigData("ActiveDirectory", callback); 1230 } 1231 1232 void doPatch(crow::Response& res, const crow::Request& req, 1233 const std::vector<std::string>& params) override 1234 { 1235 auto asyncResp = std::make_shared<AsyncResp>(res); 1236 1237 std::optional<uint32_t> unlockTimeout; 1238 std::optional<uint16_t> lockoutThreshold; 1239 std::optional<uint16_t> minPasswordLength; 1240 std::optional<uint16_t> maxPasswordLength; 1241 std::optional<nlohmann::json> ldapObject; 1242 std::optional<nlohmann::json> activeDirectoryObject; 1243 std::optional<nlohmann::json> oemObject; 1244 1245 if (!json_util::readJson( 1246 req, res, "AccountLockoutDuration", unlockTimeout, 1247 "AccountLockoutThreshold", lockoutThreshold, 1248 "MaxPasswordLength", maxPasswordLength, "MinPasswordLength", 1249 minPasswordLength, "LDAP", ldapObject, "ActiveDirectory", 1250 activeDirectoryObject, "Oem", oemObject)) 1251 { 1252 return; 1253 } 1254 1255 if (minPasswordLength) 1256 { 1257 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 1258 } 1259 1260 if (maxPasswordLength) 1261 { 1262 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1263 } 1264 1265 if (ldapObject) 1266 { 1267 handleLDAPPatch(*ldapObject, asyncResp, req, params, "LDAP"); 1268 } 1269 1270 if (std::optional<nlohmann::json> oemOpenBMCObject; 1271 oemObject && 1272 json_util::readJson(*oemObject, res, "OpenBMC", oemOpenBMCObject)) 1273 { 1274 if (std::optional<nlohmann::json> authMethodsObject; 1275 oemOpenBMCObject && 1276 json_util::readJson(*oemOpenBMCObject, res, "AuthMethods", 1277 authMethodsObject)) 1278 { 1279 if (authMethodsObject) 1280 { 1281 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1282 } 1283 } 1284 } 1285 1286 if (activeDirectoryObject) 1287 { 1288 handleLDAPPatch(*activeDirectoryObject, asyncResp, req, params, 1289 "ActiveDirectory"); 1290 } 1291 1292 if (unlockTimeout) 1293 { 1294 crow::connections::systemBus->async_method_call( 1295 [asyncResp](const boost::system::error_code ec) { 1296 if (ec) 1297 { 1298 messages::internalError(asyncResp->res); 1299 return; 1300 } 1301 messages::success(asyncResp->res); 1302 }, 1303 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1304 "org.freedesktop.DBus.Properties", "Set", 1305 "xyz.openbmc_project.User.AccountPolicy", 1306 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 1307 } 1308 if (lockoutThreshold) 1309 { 1310 crow::connections::systemBus->async_method_call( 1311 [asyncResp](const boost::system::error_code ec) { 1312 if (ec) 1313 { 1314 messages::internalError(asyncResp->res); 1315 return; 1316 } 1317 messages::success(asyncResp->res); 1318 }, 1319 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1320 "org.freedesktop.DBus.Properties", "Set", 1321 "xyz.openbmc_project.User.AccountPolicy", 1322 "MaxLoginAttemptBeforeLockout", 1323 std::variant<uint16_t>(*lockoutThreshold)); 1324 } 1325 } 1326 1327 CrowApp& app; 1328 }; 1329 1330 class AccountsCollection : public Node 1331 { 1332 public: 1333 AccountsCollection(CrowApp& app) : 1334 Node(app, "/redfish/v1/AccountService/Accounts/") 1335 { 1336 entityPrivileges = { 1337 {boost::beast::http::verb::get, 1338 {{"ConfigureUsers"}, {"ConfigureManager"}}}, 1339 {boost::beast::http::verb::head, {{"Login"}}}, 1340 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1341 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1342 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1343 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1344 } 1345 1346 private: 1347 void doGet(crow::Response& res, const crow::Request& req, 1348 const std::vector<std::string>& params) override 1349 { 1350 auto asyncResp = std::make_shared<AsyncResp>(res); 1351 res.jsonValue = {{"@odata.context", 1352 "/redfish/v1/" 1353 "$metadata#ManagerAccountCollection." 1354 "ManagerAccountCollection"}, 1355 {"@odata.id", "/redfish/v1/AccountService/Accounts"}, 1356 {"@odata.type", "#ManagerAccountCollection." 1357 "ManagerAccountCollection"}, 1358 {"Name", "Accounts Collection"}, 1359 {"Description", "BMC User Accounts"}}; 1360 1361 crow::connections::systemBus->async_method_call( 1362 [asyncResp](const boost::system::error_code ec, 1363 const ManagedObjectType& users) { 1364 if (ec) 1365 { 1366 messages::internalError(asyncResp->res); 1367 return; 1368 } 1369 1370 nlohmann::json& memberArray = 1371 asyncResp->res.jsonValue["Members"]; 1372 memberArray = nlohmann::json::array(); 1373 1374 asyncResp->res.jsonValue["Members@odata.count"] = users.size(); 1375 for (auto& user : users) 1376 { 1377 const std::string& path = 1378 static_cast<const std::string&>(user.first); 1379 std::size_t lastIndex = path.rfind("/"); 1380 if (lastIndex == std::string::npos) 1381 { 1382 lastIndex = 0; 1383 } 1384 else 1385 { 1386 lastIndex += 1; 1387 } 1388 memberArray.push_back( 1389 {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + 1390 path.substr(lastIndex)}}); 1391 } 1392 }, 1393 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1394 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1395 } 1396 void doPost(crow::Response& res, const crow::Request& req, 1397 const std::vector<std::string>& params) override 1398 { 1399 auto asyncResp = std::make_shared<AsyncResp>(res); 1400 1401 std::string username; 1402 std::string password; 1403 std::optional<std::string> roleId("User"); 1404 std::optional<bool> enabled = true; 1405 if (!json_util::readJson(req, res, "UserName", username, "Password", 1406 password, "RoleId", roleId, "Enabled", 1407 enabled)) 1408 { 1409 return; 1410 } 1411 1412 std::string priv = getPrivilegeFromRoleId(*roleId); 1413 if (priv.empty()) 1414 { 1415 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1416 return; 1417 } 1418 roleId = priv; 1419 1420 // Reading AllGroups property 1421 crow::connections::systemBus->async_method_call( 1422 [asyncResp, username, password{std::move(password)}, roleId, 1423 enabled](const boost::system::error_code ec, 1424 const std::variant<std::vector<std::string>>& allGroups) { 1425 if (ec) 1426 { 1427 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1428 messages::internalError(asyncResp->res); 1429 return; 1430 } 1431 1432 const std::vector<std::string>* allGroupsList = 1433 std::get_if<std::vector<std::string>>(&allGroups); 1434 1435 if (allGroupsList == nullptr || allGroupsList->empty()) 1436 { 1437 messages::internalError(asyncResp->res); 1438 return; 1439 } 1440 1441 crow::connections::systemBus->async_method_call( 1442 [asyncResp, username, password{std::move(password)}]( 1443 const boost::system::error_code ec, 1444 sdbusplus::message::message& m) { 1445 if (ec) 1446 { 1447 userErrorMessageHandler(m.get_error(), asyncResp, 1448 username, ""); 1449 return; 1450 } 1451 1452 if (pamUpdatePassword(username, password) != 1453 PAM_SUCCESS) 1454 { 1455 // At this point we have a user that's been created, 1456 // but the password set failed.Something is wrong, 1457 // so delete the user that we've already created 1458 crow::connections::systemBus->async_method_call( 1459 [asyncResp, 1460 password](const boost::system::error_code ec) { 1461 if (ec) 1462 { 1463 messages::internalError(asyncResp->res); 1464 return; 1465 } 1466 1467 // If password is invalid 1468 messages::propertyValueFormatError( 1469 asyncResp->res, password, "Password"); 1470 }, 1471 "xyz.openbmc_project.User.Manager", 1472 "/xyz/openbmc_project/user/" + username, 1473 "xyz.openbmc_project.Object.Delete", "Delete"); 1474 1475 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1476 return; 1477 } 1478 1479 messages::created(asyncResp->res); 1480 asyncResp->res.addHeader( 1481 "Location", 1482 "/redfish/v1/AccountService/Accounts/" + username); 1483 }, 1484 "xyz.openbmc_project.User.Manager", 1485 "/xyz/openbmc_project/user", 1486 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1487 *allGroupsList, *roleId, *enabled); 1488 }, 1489 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1490 "org.freedesktop.DBus.Properties", "Get", 1491 "xyz.openbmc_project.User.Manager", "AllGroups"); 1492 } 1493 }; 1494 1495 class ManagerAccount : public Node 1496 { 1497 public: 1498 ManagerAccount(CrowApp& app) : 1499 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1500 { 1501 entityPrivileges = { 1502 {boost::beast::http::verb::get, 1503 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1504 {boost::beast::http::verb::head, {{"Login"}}}, 1505 {boost::beast::http::verb::patch, 1506 {{"ConfigureUsers"}, {"ConfigureSelf"}}}, 1507 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1508 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1509 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1510 } 1511 1512 private: 1513 void doGet(crow::Response& res, const crow::Request& req, 1514 const std::vector<std::string>& params) override 1515 { 1516 auto asyncResp = std::make_shared<AsyncResp>(res); 1517 1518 if (params.size() != 1) 1519 { 1520 messages::internalError(asyncResp->res); 1521 return; 1522 } 1523 1524 // Perform a proper ConfigureSelf authority check. If the 1525 // user is operating on an account not their own, then their 1526 // ConfigureSelf privilege does not apply. In this case, 1527 // perform the authority check again without the user's 1528 // ConfigureSelf privilege. 1529 if (req.session->username != params[0]) 1530 { 1531 if (!isAllowedWithoutConfigureSelf(req)) 1532 { 1533 BMCWEB_LOG_DEBUG << "GET Account denied access"; 1534 messages::insufficientPrivilege(asyncResp->res); 1535 return; 1536 } 1537 } 1538 1539 crow::connections::systemBus->async_method_call( 1540 [asyncResp, accountName{std::string(params[0])}]( 1541 const boost::system::error_code ec, 1542 const ManagedObjectType& users) { 1543 if (ec) 1544 { 1545 messages::internalError(asyncResp->res); 1546 return; 1547 } 1548 auto userIt = users.begin(); 1549 1550 for (; userIt != users.end(); userIt++) 1551 { 1552 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1553 { 1554 break; 1555 } 1556 } 1557 if (userIt == users.end()) 1558 { 1559 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1560 accountName); 1561 return; 1562 } 1563 1564 asyncResp->res.jsonValue = { 1565 {"@odata.context", 1566 "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, 1567 {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, 1568 {"Name", "User Account"}, 1569 {"Description", "User Account"}, 1570 {"Password", nullptr}}; 1571 1572 for (const auto& interface : userIt->second) 1573 { 1574 if (interface.first == 1575 "xyz.openbmc_project.User.Attributes") 1576 { 1577 for (const auto& property : interface.second) 1578 { 1579 if (property.first == "UserEnabled") 1580 { 1581 const bool* userEnabled = 1582 std::get_if<bool>(&property.second); 1583 if (userEnabled == nullptr) 1584 { 1585 BMCWEB_LOG_ERROR 1586 << "UserEnabled wasn't a bool"; 1587 messages::internalError(asyncResp->res); 1588 return; 1589 } 1590 asyncResp->res.jsonValue["Enabled"] = 1591 *userEnabled; 1592 } 1593 else if (property.first == 1594 "UserLockedForFailedAttempt") 1595 { 1596 const bool* userLocked = 1597 std::get_if<bool>(&property.second); 1598 if (userLocked == nullptr) 1599 { 1600 BMCWEB_LOG_ERROR << "UserLockedForF" 1601 "ailedAttempt " 1602 "wasn't a bool"; 1603 messages::internalError(asyncResp->res); 1604 return; 1605 } 1606 asyncResp->res.jsonValue["Locked"] = 1607 *userLocked; 1608 asyncResp->res.jsonValue 1609 ["Locked@Redfish.AllowableValues"] = { 1610 "false"}; 1611 } 1612 else if (property.first == "UserPrivilege") 1613 { 1614 const std::string* userPrivPtr = 1615 std::get_if<std::string>(&property.second); 1616 if (userPrivPtr == nullptr) 1617 { 1618 BMCWEB_LOG_ERROR 1619 << "UserPrivilege wasn't a " 1620 "string"; 1621 messages::internalError(asyncResp->res); 1622 return; 1623 } 1624 std::string role = 1625 getRoleIdFromPrivilege(*userPrivPtr); 1626 if (role.empty()) 1627 { 1628 BMCWEB_LOG_ERROR << "Invalid user role"; 1629 messages::internalError(asyncResp->res); 1630 return; 1631 } 1632 asyncResp->res.jsonValue["RoleId"] = role; 1633 1634 asyncResp->res.jsonValue["Links"]["Role"] = { 1635 {"@odata.id", "/redfish/v1/AccountService/" 1636 "Roles/" + 1637 role}}; 1638 } 1639 } 1640 } 1641 } 1642 1643 asyncResp->res.jsonValue["@odata.id"] = 1644 "/redfish/v1/AccountService/Accounts/" + accountName; 1645 asyncResp->res.jsonValue["Id"] = accountName; 1646 asyncResp->res.jsonValue["UserName"] = accountName; 1647 }, 1648 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1649 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1650 } 1651 1652 void doPatch(crow::Response& res, const crow::Request& req, 1653 const std::vector<std::string>& params) override 1654 { 1655 auto asyncResp = std::make_shared<AsyncResp>(res); 1656 if (params.size() != 1) 1657 { 1658 messages::internalError(asyncResp->res); 1659 return; 1660 } 1661 1662 std::optional<std::string> newUserName; 1663 std::optional<std::string> password; 1664 std::optional<bool> enabled; 1665 std::optional<std::string> roleId; 1666 std::optional<bool> locked; 1667 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1668 password, "RoleId", roleId, "Enabled", enabled, 1669 "Locked", locked)) 1670 { 1671 return; 1672 } 1673 1674 const std::string& username = params[0]; 1675 1676 // Perform a proper ConfigureSelf authority check. If the 1677 // session is being used to PATCH a property other than 1678 // Password, then the ConfigureSelf privilege does not apply. 1679 // If the user is operating on an account not their own, then 1680 // their ConfigureSelf privilege does not apply. In either 1681 // case, perform the authority check again without the user's 1682 // ConfigureSelf privilege. 1683 if ((username != req.session->username) || 1684 (newUserName || enabled || roleId || locked)) 1685 { 1686 if (!isAllowedWithoutConfigureSelf(req)) 1687 { 1688 BMCWEB_LOG_WARNING << "PATCH Password denied access"; 1689 asyncResp->res.clear(); 1690 messages::insufficientPrivilege(asyncResp->res); 1691 return; 1692 } 1693 } 1694 1695 // if user name is not provided in the patch method or if it 1696 // matches the user name in the URI, then we are treating it as updating 1697 // user properties other then username. If username provided doesn't 1698 // match the URI, then we are treating this as user rename request. 1699 if (!newUserName || (newUserName.value() == username)) 1700 { 1701 updateUserProperties(asyncResp, username, password, enabled, roleId, 1702 locked); 1703 return; 1704 } 1705 else 1706 { 1707 crow::connections::systemBus->async_method_call( 1708 [this, asyncResp, username, password(std::move(password)), 1709 roleId(std::move(roleId)), enabled(std::move(enabled)), 1710 newUser{std::string(*newUserName)}, 1711 locked(std::move(locked))](const boost::system::error_code ec, 1712 sdbusplus::message::message& m) { 1713 if (ec) 1714 { 1715 userErrorMessageHandler(m.get_error(), asyncResp, 1716 newUser, username); 1717 return; 1718 } 1719 1720 updateUserProperties(asyncResp, newUser, password, enabled, 1721 roleId, locked); 1722 }, 1723 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1724 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1725 *newUserName); 1726 } 1727 } 1728 1729 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1730 const std::string& username, 1731 std::optional<std::string> password, 1732 std::optional<bool> enabled, 1733 std::optional<std::string> roleId, 1734 std::optional<bool> locked) 1735 { 1736 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1737 dbus::utility::escapePathForDbus(dbusObjectPath); 1738 1739 dbus::utility::checkDbusPathExists( 1740 dbusObjectPath, 1741 [dbusObjectPath(std::move(dbusObjectPath)), username, 1742 password(std::move(password)), roleId(std::move(roleId)), 1743 enabled(std::move(enabled)), locked(std::move(locked)), 1744 asyncResp{std::move(asyncResp)}](int rc) { 1745 if (!rc) 1746 { 1747 messages::resourceNotFound( 1748 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1749 username); 1750 return; 1751 } 1752 1753 if (password) 1754 { 1755 int retval = pamUpdatePassword(username, *password); 1756 1757 if (retval == PAM_USER_UNKNOWN) 1758 { 1759 messages::resourceNotFound( 1760 asyncResp->res, 1761 "#ManagerAccount.v1_0_3.ManagerAccount", username); 1762 } 1763 else if (retval == PAM_AUTHTOK_ERR) 1764 { 1765 // If password is invalid 1766 messages::propertyValueFormatError( 1767 asyncResp->res, *password, "Password"); 1768 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1769 } 1770 else if (retval != PAM_SUCCESS) 1771 { 1772 messages::internalError(asyncResp->res); 1773 return; 1774 } 1775 } 1776 1777 if (enabled) 1778 { 1779 crow::connections::systemBus->async_method_call( 1780 [asyncResp](const boost::system::error_code ec) { 1781 if (ec) 1782 { 1783 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1784 << ec; 1785 messages::internalError(asyncResp->res); 1786 return; 1787 } 1788 messages::success(asyncResp->res); 1789 return; 1790 }, 1791 "xyz.openbmc_project.User.Manager", 1792 dbusObjectPath.c_str(), 1793 "org.freedesktop.DBus.Properties", "Set", 1794 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1795 std::variant<bool>{*enabled}); 1796 } 1797 1798 if (roleId) 1799 { 1800 std::string priv = getPrivilegeFromRoleId(*roleId); 1801 if (priv.empty()) 1802 { 1803 messages::propertyValueNotInList(asyncResp->res, 1804 *roleId, "RoleId"); 1805 return; 1806 } 1807 1808 crow::connections::systemBus->async_method_call( 1809 [asyncResp](const boost::system::error_code ec) { 1810 if (ec) 1811 { 1812 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1813 << ec; 1814 messages::internalError(asyncResp->res); 1815 return; 1816 } 1817 messages::success(asyncResp->res); 1818 }, 1819 "xyz.openbmc_project.User.Manager", 1820 dbusObjectPath.c_str(), 1821 "org.freedesktop.DBus.Properties", "Set", 1822 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1823 std::variant<std::string>{priv}); 1824 } 1825 1826 if (locked) 1827 { 1828 // admin can unlock the account which is locked by 1829 // successive authentication failures but admin should 1830 // not be allowed to lock an account. 1831 if (*locked) 1832 { 1833 messages::propertyValueNotInList(asyncResp->res, "true", 1834 "Locked"); 1835 return; 1836 } 1837 1838 crow::connections::systemBus->async_method_call( 1839 [asyncResp](const boost::system::error_code ec) { 1840 if (ec) 1841 { 1842 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1843 << ec; 1844 messages::internalError(asyncResp->res); 1845 return; 1846 } 1847 messages::success(asyncResp->res); 1848 return; 1849 }, 1850 "xyz.openbmc_project.User.Manager", 1851 dbusObjectPath.c_str(), 1852 "org.freedesktop.DBus.Properties", "Set", 1853 "xyz.openbmc_project.User.Attributes", 1854 "UserLockedForFailedAttempt", 1855 sdbusplus::message::variant<bool>{*locked}); 1856 } 1857 }); 1858 } 1859 1860 void doDelete(crow::Response& res, const crow::Request& req, 1861 const std::vector<std::string>& params) override 1862 { 1863 auto asyncResp = std::make_shared<AsyncResp>(res); 1864 1865 if (params.size() != 1) 1866 { 1867 messages::internalError(asyncResp->res); 1868 return; 1869 } 1870 1871 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1872 1873 crow::connections::systemBus->async_method_call( 1874 [asyncResp, username{std::move(params[0])}]( 1875 const boost::system::error_code ec) { 1876 if (ec) 1877 { 1878 messages::resourceNotFound( 1879 asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", 1880 username); 1881 return; 1882 } 1883 1884 messages::accountRemoved(asyncResp->res); 1885 }, 1886 "xyz.openbmc_project.User.Manager", userPath, 1887 "xyz.openbmc_project.Object.Delete", "Delete"); 1888 } 1889 }; 1890 1891 } // namespace redfish 1892