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