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 <persistent_data.hpp> 23 #include <utils/json_utils.hpp> 24 25 #include <variant> 26 27 namespace redfish 28 { 29 30 constexpr const char* ldapConfigObjectName = 31 "/xyz/openbmc_project/user/ldap/openldap"; 32 constexpr const char* adConfigObject = 33 "/xyz/openbmc_project/user/ldap/active_directory"; 34 35 constexpr const char* ldapRootObject = "/xyz/openbmc_project/user/ldap"; 36 constexpr const char* ldapDbusService = "xyz.openbmc_project.Ldap.Config"; 37 constexpr const char* ldapConfigInterface = 38 "xyz.openbmc_project.User.Ldap.Config"; 39 constexpr const char* ldapCreateInterface = 40 "xyz.openbmc_project.User.Ldap.Create"; 41 constexpr const char* ldapEnableInterface = "xyz.openbmc_project.Object.Enable"; 42 constexpr const char* ldapPrivMapperInterface = 43 "xyz.openbmc_project.User.PrivilegeMapper"; 44 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; 45 constexpr const char* propertyInterface = "org.freedesktop.DBus.Properties"; 46 constexpr const char* mapperBusName = "xyz.openbmc_project.ObjectMapper"; 47 constexpr const char* mapperObjectPath = "/xyz/openbmc_project/object_mapper"; 48 constexpr const char* mapperIntf = "xyz.openbmc_project.ObjectMapper"; 49 50 struct LDAPRoleMapData 51 { 52 std::string groupName; 53 std::string privilege; 54 }; 55 56 struct LDAPConfigData 57 { 58 std::string uri{}; 59 std::string bindDN{}; 60 std::string baseDN{}; 61 std::string searchScope{}; 62 std::string serverType{}; 63 bool serviceEnabled = false; 64 std::string userNameAttribute{}; 65 std::string groupAttribute{}; 66 std::vector<std::pair<std::string, LDAPRoleMapData>> groupRoleList; 67 }; 68 69 using DbusVariantType = std::variant<bool, int32_t, std::string>; 70 71 using DbusInterfaceType = boost::container::flat_map< 72 std::string, boost::container::flat_map<std::string, DbusVariantType>>; 73 74 using ManagedObjectType = 75 std::vector<std::pair<sdbusplus::message::object_path, DbusInterfaceType>>; 76 77 using GetObjectType = 78 std::vector<std::pair<std::string, std::vector<std::string>>>; 79 80 inline std::string getRoleIdFromPrivilege(std::string_view role) 81 { 82 if (role == "priv-admin") 83 { 84 return "Administrator"; 85 } 86 if (role == "priv-user") 87 { 88 return "ReadOnly"; 89 } 90 if (role == "priv-operator") 91 { 92 return "Operator"; 93 } 94 if ((role == "") || (role == "priv-noaccess")) 95 { 96 return "NoAccess"; 97 } 98 return ""; 99 } 100 inline std::string getPrivilegeFromRoleId(std::string_view role) 101 { 102 if (role == "Administrator") 103 { 104 return "priv-admin"; 105 } 106 if (role == "ReadOnly") 107 { 108 return "priv-user"; 109 } 110 if (role == "Operator") 111 { 112 return "priv-operator"; 113 } 114 if ((role == "NoAccess") || (role == "")) 115 { 116 return "priv-noaccess"; 117 } 118 return ""; 119 } 120 121 inline void userErrorMessageHandler(const sd_bus_error* e, 122 std::shared_ptr<AsyncResp> asyncResp, 123 const std::string& newUser, 124 const std::string& username) 125 { 126 const char* errorMessage = e->name; 127 if (e == nullptr) 128 { 129 messages::internalError(asyncResp->res); 130 return; 131 } 132 133 if (strcmp(errorMessage, 134 "xyz.openbmc_project.User.Common.Error.UserNameExists") == 0) 135 { 136 messages::resourceAlreadyExists(asyncResp->res, 137 "#ManagerAccount.v1_4_0.ManagerAccount", 138 "UserName", newUser); 139 } 140 else if (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 141 "UserNameDoesNotExist") == 0) 142 { 143 messages::resourceNotFound( 144 asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", username); 145 } 146 else if ((strcmp(errorMessage, 147 "xyz.openbmc_project.Common.Error.InvalidArgument") == 148 0) || 149 (strcmp(errorMessage, "xyz.openbmc_project.User.Common.Error." 150 "UserNameGroupFail") == 0)) 151 { 152 messages::propertyValueFormatError(asyncResp->res, newUser, "UserName"); 153 } 154 else if (strcmp(errorMessage, 155 "xyz.openbmc_project.User.Common.Error.NoResource") == 0) 156 { 157 messages::createLimitReachedForResource(asyncResp->res); 158 } 159 else 160 { 161 messages::internalError(asyncResp->res); 162 } 163 164 return; 165 } 166 167 inline 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(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 inline 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 = ldapConfigObjectName; 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", *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 if (ec || resp.empty()) 391 { 392 BMCWEB_LOG_ERROR << "DBUS response error during getting of " 393 "service name: " 394 << ec; 395 LDAPConfigData empty{}; 396 callback(false, empty, 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 ldapConfigObjectName, interfaces); 553 } 554 555 class AccountService : public Node 556 { 557 public: 558 AccountService(App& app) : Node(app, "/redfish/v1/AccountService/") 559 { 560 entityPrivileges = { 561 {boost::beast::http::verb::get, {{"Login"}}}, 562 {boost::beast::http::verb::head, {{"Login"}}}, 563 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 564 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 565 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 566 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 567 } 568 569 private: 570 /** 571 * @brief parses the authentication section under the LDAP 572 * @param input JSON data 573 * @param asyncResp pointer to the JSON response 574 * @param userName userName to be filled from the given JSON. 575 * @param password password to be filled from the given JSON. 576 */ 577 void 578 parseLDAPAuthenticationJson(nlohmann::json input, 579 const std::shared_ptr<AsyncResp>& asyncResp, 580 std::optional<std::string>& username, 581 std::optional<std::string>& password) 582 { 583 std::optional<std::string> authType; 584 585 if (!json_util::readJson(input, asyncResp->res, "AuthenticationType", 586 authType, "Username", username, "Password", 587 password)) 588 { 589 return; 590 } 591 if (!authType) 592 { 593 return; 594 } 595 if (*authType != "UsernameAndPassword") 596 { 597 messages::propertyValueNotInList(asyncResp->res, *authType, 598 "AuthenticationType"); 599 return; 600 } 601 } 602 /** 603 * @brief parses the LDAPService section under the LDAP 604 * @param input JSON data 605 * @param asyncResp pointer to the JSON response 606 * @param baseDNList baseDN to be filled from the given JSON. 607 * @param userNameAttribute userName to be filled from the given JSON. 608 * @param groupaAttribute password to be filled from the given JSON. 609 */ 610 611 void parseLDAPServiceJson( 612 nlohmann::json input, const std::shared_ptr<AsyncResp>& asyncResp, 613 std::optional<std::vector<std::string>>& baseDNList, 614 std::optional<std::string>& userNameAttribute, 615 std::optional<std::string>& groupsAttribute) 616 { 617 std::optional<nlohmann::json> searchSettings; 618 619 if (!json_util::readJson(input, asyncResp->res, "SearchSettings", 620 searchSettings)) 621 { 622 return; 623 } 624 if (!searchSettings) 625 { 626 return; 627 } 628 if (!json_util::readJson(*searchSettings, asyncResp->res, 629 "BaseDistinguishedNames", baseDNList, 630 "UsernameAttribute", userNameAttribute, 631 "GroupsAttribute", groupsAttribute)) 632 { 633 return; 634 } 635 } 636 /** 637 * @brief updates the LDAP server address and updates the 638 json response with the new value. 639 * @param serviceAddressList address to be updated. 640 * @param asyncResp pointer to the JSON response 641 * @param ldapServerElementName Type of LDAP 642 server(openLDAP/ActiveDirectory) 643 */ 644 645 void handleServiceAddressPatch( 646 const std::vector<std::string>& serviceAddressList, 647 const std::shared_ptr<AsyncResp>& asyncResp, 648 const std::string& ldapServerElementName, 649 const std::string& ldapConfigObject) 650 { 651 crow::connections::systemBus->async_method_call( 652 [asyncResp, ldapServerElementName, 653 serviceAddressList](const boost::system::error_code ec) { 654 if (ec) 655 { 656 BMCWEB_LOG_DEBUG 657 << "Error Occurred in updating the service address"; 658 messages::internalError(asyncResp->res); 659 return; 660 } 661 std::vector<std::string> modifiedserviceAddressList = { 662 serviceAddressList.front()}; 663 asyncResp->res 664 .jsonValue[ldapServerElementName]["ServiceAddresses"] = 665 modifiedserviceAddressList; 666 if ((serviceAddressList).size() > 1) 667 { 668 messages::propertyValueModified(asyncResp->res, 669 "ServiceAddresses", 670 serviceAddressList.front()); 671 } 672 BMCWEB_LOG_DEBUG << "Updated the service address"; 673 }, 674 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 675 ldapConfigInterface, "LDAPServerURI", 676 std::variant<std::string>(serviceAddressList.front())); 677 } 678 /** 679 * @brief updates the LDAP Bind DN and updates the 680 json response with the new value. 681 * @param username name of the user which needs to be updated. 682 * @param asyncResp pointer to the JSON response 683 * @param ldapServerElementName Type of LDAP 684 server(openLDAP/ActiveDirectory) 685 */ 686 687 void handleUserNamePatch(const std::string& username, 688 const std::shared_ptr<AsyncResp>& asyncResp, 689 const std::string& ldapServerElementName, 690 const std::string& ldapConfigObject) 691 { 692 crow::connections::systemBus->async_method_call( 693 [asyncResp, username, 694 ldapServerElementName](const boost::system::error_code ec) { 695 if (ec) 696 { 697 BMCWEB_LOG_DEBUG 698 << "Error occurred in updating the username"; 699 messages::internalError(asyncResp->res); 700 return; 701 } 702 asyncResp->res.jsonValue[ldapServerElementName] 703 ["Authentication"]["Username"] = 704 username; 705 BMCWEB_LOG_DEBUG << "Updated the username"; 706 }, 707 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 708 ldapConfigInterface, "LDAPBindDN", 709 std::variant<std::string>(username)); 710 } 711 712 /** 713 * @brief updates the LDAP password 714 * @param password : ldap password which needs to be updated. 715 * @param asyncResp pointer to the JSON response 716 * @param ldapServerElementName Type of LDAP 717 * server(openLDAP/ActiveDirectory) 718 */ 719 720 void handlePasswordPatch(const std::string& password, 721 const std::shared_ptr<AsyncResp>& asyncResp, 722 const std::string& ldapServerElementName, 723 const std::string& ldapConfigObject) 724 { 725 crow::connections::systemBus->async_method_call( 726 [asyncResp, password, 727 ldapServerElementName](const boost::system::error_code ec) { 728 if (ec) 729 { 730 BMCWEB_LOG_DEBUG 731 << "Error occurred in updating the password"; 732 messages::internalError(asyncResp->res); 733 return; 734 } 735 asyncResp->res.jsonValue[ldapServerElementName] 736 ["Authentication"]["Password"] = ""; 737 BMCWEB_LOG_DEBUG << "Updated the password"; 738 }, 739 ldapDbusService, ldapConfigObject, propertyInterface, "Set", 740 ldapConfigInterface, "LDAPBindDNPassword", 741 std::variant<std::string>(password)); 742 } 743 744 /** 745 * @brief updates the LDAP BaseDN and updates the 746 json response with the new value. 747 * @param baseDNList baseDN list which needs to be updated. 748 * @param asyncResp pointer to the JSON response 749 * @param ldapServerElementName Type of LDAP 750 server(openLDAP/ActiveDirectory) 751 */ 752 753 void handleBaseDNPatch(const std::vector<std::string>& baseDNList, 754 const std::shared_ptr<AsyncResp>& asyncResp, 755 const std::string& ldapServerElementName, 756 const std::string& ldapConfigObject) 757 { 758 crow::connections::systemBus->async_method_call( 759 [asyncResp, baseDNList, 760 ldapServerElementName](const boost::system::error_code ec) { 761 if (ec) 762 { 763 BMCWEB_LOG_DEBUG 764 << "Error Occurred 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 Occurred 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 Occurred 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 Occurred 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 persistent_data::AuthConfigMethods authMethodsConfig = 911 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 912 913 if (basicAuth) 914 { 915 authMethodsConfig.basic = *basicAuth; 916 } 917 918 if (cookie) 919 { 920 authMethodsConfig.cookie = *cookie; 921 } 922 923 if (sessionToken) 924 { 925 authMethodsConfig.sessionToken = *sessionToken; 926 } 927 928 if (xToken) 929 { 930 authMethodsConfig.xtoken = *xToken; 931 } 932 933 if (tls) 934 { 935 authMethodsConfig.tls = *tls; 936 } 937 938 if (!authMethodsConfig.basic && !authMethodsConfig.cookie && 939 !authMethodsConfig.sessionToken && !authMethodsConfig.xtoken && 940 !authMethodsConfig.tls) 941 { 942 // Do not allow user to disable everything 943 messages::actionNotSupported(asyncResp->res, 944 "of disabling all available methods"); 945 return; 946 } 947 948 persistent_data::SessionStore::getInstance().updateAuthMethodsConfig( 949 authMethodsConfig); 950 // Save configuration immediately 951 persistent_data::getConfig().writeData(); 952 953 messages::success(asyncResp->res); 954 } 955 956 /** 957 * @brief Get the required values from the given JSON, validates the 958 * value and create the LDAP config object. 959 * @param input JSON data 960 * @param asyncResp pointer to the JSON response 961 * @param serverType Type of LDAP server(openLDAP/ActiveDirectory) 962 */ 963 964 void handleLDAPPatch(nlohmann::json& input, 965 const std::shared_ptr<AsyncResp>& asyncResp, 966 const std::string& serverType) 967 { 968 std::string dbusObjectPath; 969 if (serverType == "ActiveDirectory") 970 { 971 dbusObjectPath = adConfigObject; 972 } 973 else if (serverType == "LDAP") 974 { 975 dbusObjectPath = ldapConfigObjectName; 976 } 977 else 978 { 979 return; 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& serverT) { 1048 if (!success) 1049 { 1050 messages::internalError(asyncResp->res); 1051 return; 1052 } 1053 parseLDAPConfigData(asyncResp->res.jsonValue, confData, serverT); 1054 if (confData.serviceEnabled) 1055 { 1056 // Disable the service first and update the rest of 1057 // the properties. 1058 handleServiceEnablePatch(false, asyncResp, serverT, 1059 dbusObjectPath); 1060 } 1061 1062 if (serviceAddressList) 1063 { 1064 handleServiceAddressPatch(*serviceAddressList, asyncResp, 1065 serverT, dbusObjectPath); 1066 } 1067 if (userName) 1068 { 1069 handleUserNamePatch(*userName, asyncResp, serverT, 1070 dbusObjectPath); 1071 } 1072 if (password) 1073 { 1074 handlePasswordPatch(*password, asyncResp, serverT, 1075 dbusObjectPath); 1076 } 1077 1078 if (baseDNList) 1079 { 1080 handleBaseDNPatch(*baseDNList, asyncResp, serverT, 1081 dbusObjectPath); 1082 } 1083 if (userNameAttribute) 1084 { 1085 handleUserNameAttrPatch(*userNameAttribute, asyncResp, serverT, 1086 dbusObjectPath); 1087 } 1088 if (groupsAttribute) 1089 { 1090 handleGroupNameAttrPatch(*groupsAttribute, asyncResp, serverT, 1091 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 serverT, 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 serverT, dbusObjectPath); 1111 } 1112 1113 if (remoteRoleMapData) 1114 { 1115 std::vector<nlohmann::json> remoteRoleMap = 1116 std::move(*remoteRoleMapData); 1117 1118 handleRoleMapPatch(asyncResp, confData.groupRoleList, serverT, 1119 remoteRoleMap); 1120 } 1121 }); 1122 } 1123 1124 void doGet(crow::Response& res, const crow::Request&, 1125 const std::vector<std::string>&) override 1126 { 1127 const persistent_data::AuthConfigMethods& authMethodsConfig = 1128 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1129 1130 auto asyncResp = std::make_shared<AsyncResp>(res); 1131 res.jsonValue = { 1132 {"@odata.id", "/redfish/v1/AccountService"}, 1133 {"@odata.type", "#AccountService." 1134 "v1_5_0.AccountService"}, 1135 {"Id", "AccountService"}, 1136 {"Name", "Account Service"}, 1137 {"Description", "Account Service"}, 1138 {"ServiceEnabled", true}, 1139 {"MaxPasswordLength", 20}, 1140 {"Accounts", 1141 {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, 1142 {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}, 1143 {"Oem", 1144 {{"OpenBMC", 1145 {{"@odata.type", "#OemAccountService.v1_0_0.AccountService"}, 1146 {"AuthMethods", 1147 { 1148 {"BasicAuth", authMethodsConfig.basic}, 1149 {"SessionToken", authMethodsConfig.sessionToken}, 1150 {"XToken", authMethodsConfig.xtoken}, 1151 {"Cookie", authMethodsConfig.cookie}, 1152 {"TLS", authMethodsConfig.tls}, 1153 }}}}}}, 1154 {"LDAP", 1155 {{"Certificates", 1156 {{"@odata.id", 1157 "/redfish/v1/AccountService/LDAP/Certificates"}}}}}}; 1158 crow::connections::systemBus->async_method_call( 1159 [asyncResp]( 1160 const boost::system::error_code ec, 1161 const std::vector<std::pair< 1162 std::string, std::variant<uint32_t, uint16_t, uint8_t>>>& 1163 propertiesList) { 1164 if (ec) 1165 { 1166 messages::internalError(asyncResp->res); 1167 return; 1168 } 1169 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1170 << "properties for AccountService"; 1171 for (const std::pair<std::string, 1172 std::variant<uint32_t, uint16_t, uint8_t>>& 1173 property : propertiesList) 1174 { 1175 if (property.first == "MinPasswordLength") 1176 { 1177 const uint8_t* value = 1178 std::get_if<uint8_t>(&property.second); 1179 if (value != nullptr) 1180 { 1181 asyncResp->res.jsonValue["MinPasswordLength"] = 1182 *value; 1183 } 1184 } 1185 if (property.first == "AccountUnlockTimeout") 1186 { 1187 const uint32_t* value = 1188 std::get_if<uint32_t>(&property.second); 1189 if (value != nullptr) 1190 { 1191 asyncResp->res.jsonValue["AccountLockoutDuration"] = 1192 *value; 1193 } 1194 } 1195 if (property.first == "MaxLoginAttemptBeforeLockout") 1196 { 1197 const uint16_t* value = 1198 std::get_if<uint16_t>(&property.second); 1199 if (value != nullptr) 1200 { 1201 asyncResp->res 1202 .jsonValue["AccountLockoutThreshold"] = *value; 1203 } 1204 } 1205 } 1206 }, 1207 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1208 "org.freedesktop.DBus.Properties", "GetAll", 1209 "xyz.openbmc_project.User.AccountPolicy"); 1210 1211 auto callback = [asyncResp](bool success, LDAPConfigData& confData, 1212 const std::string& ldapType) { 1213 if (!success) 1214 { 1215 return; 1216 } 1217 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1218 }; 1219 1220 getLDAPConfigData("LDAP", callback); 1221 getLDAPConfigData("ActiveDirectory", callback); 1222 } 1223 1224 void doPatch(crow::Response& res, const crow::Request& req, 1225 const std::vector<std::string>&) override 1226 { 1227 auto asyncResp = std::make_shared<AsyncResp>(res); 1228 1229 std::optional<uint32_t> unlockTimeout; 1230 std::optional<uint16_t> lockoutThreshold; 1231 std::optional<uint16_t> minPasswordLength; 1232 std::optional<uint16_t> maxPasswordLength; 1233 std::optional<nlohmann::json> ldapObject; 1234 std::optional<nlohmann::json> activeDirectoryObject; 1235 std::optional<nlohmann::json> oemObject; 1236 1237 if (!json_util::readJson( 1238 req, res, "AccountLockoutDuration", unlockTimeout, 1239 "AccountLockoutThreshold", lockoutThreshold, 1240 "MaxPasswordLength", maxPasswordLength, "MinPasswordLength", 1241 minPasswordLength, "LDAP", ldapObject, "ActiveDirectory", 1242 activeDirectoryObject, "Oem", oemObject)) 1243 { 1244 return; 1245 } 1246 1247 if (minPasswordLength) 1248 { 1249 messages::propertyNotWritable(asyncResp->res, "MinPasswordLength"); 1250 } 1251 1252 if (maxPasswordLength) 1253 { 1254 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1255 } 1256 1257 if (ldapObject) 1258 { 1259 handleLDAPPatch(*ldapObject, asyncResp, "LDAP"); 1260 } 1261 1262 if (std::optional<nlohmann::json> oemOpenBMCObject; 1263 oemObject && 1264 json_util::readJson(*oemObject, res, "OpenBMC", oemOpenBMCObject)) 1265 { 1266 if (std::optional<nlohmann::json> authMethodsObject; 1267 oemOpenBMCObject && 1268 json_util::readJson(*oemOpenBMCObject, res, "AuthMethods", 1269 authMethodsObject)) 1270 { 1271 if (authMethodsObject) 1272 { 1273 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1274 } 1275 } 1276 } 1277 1278 if (activeDirectoryObject) 1279 { 1280 handleLDAPPatch(*activeDirectoryObject, asyncResp, 1281 "ActiveDirectory"); 1282 } 1283 1284 if (unlockTimeout) 1285 { 1286 crow::connections::systemBus->async_method_call( 1287 [asyncResp](const boost::system::error_code ec) { 1288 if (ec) 1289 { 1290 messages::internalError(asyncResp->res); 1291 return; 1292 } 1293 messages::success(asyncResp->res); 1294 }, 1295 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1296 "org.freedesktop.DBus.Properties", "Set", 1297 "xyz.openbmc_project.User.AccountPolicy", 1298 "AccountUnlockTimeout", std::variant<uint32_t>(*unlockTimeout)); 1299 } 1300 if (lockoutThreshold) 1301 { 1302 crow::connections::systemBus->async_method_call( 1303 [asyncResp](const boost::system::error_code ec) { 1304 if (ec) 1305 { 1306 messages::internalError(asyncResp->res); 1307 return; 1308 } 1309 messages::success(asyncResp->res); 1310 }, 1311 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1312 "org.freedesktop.DBus.Properties", "Set", 1313 "xyz.openbmc_project.User.AccountPolicy", 1314 "MaxLoginAttemptBeforeLockout", 1315 std::variant<uint16_t>(*lockoutThreshold)); 1316 } 1317 } 1318 }; 1319 1320 class AccountsCollection : public Node 1321 { 1322 public: 1323 AccountsCollection(App& app) : 1324 Node(app, "/redfish/v1/AccountService/Accounts/") 1325 { 1326 entityPrivileges = { 1327 // According to the PrivilegeRegistry, GET should actually be 1328 // "Login". A "Login" only privilege would return an empty "Members" 1329 // list. Not going to worry about this since none of the defined 1330 // roles are just "Login". E.g. Readonly is {"Login", 1331 // "ConfigureSelf"}. In the rare event anyone defines a role that 1332 // has Login but not ConfigureSelf, implement this. 1333 {boost::beast::http::verb::get, 1334 {{"ConfigureUsers"}, {"ConfigureSelf"}}}, 1335 {boost::beast::http::verb::head, {{"Login"}}}, 1336 {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, 1337 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1338 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1339 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1340 } 1341 1342 private: 1343 void doGet(crow::Response& res, const crow::Request& req, 1344 const std::vector<std::string>&) override 1345 { 1346 auto asyncResp = std::make_shared<AsyncResp>(res); 1347 res.jsonValue = {{"@odata.id", "/redfish/v1/AccountService/Accounts"}, 1348 {"@odata.type", "#ManagerAccountCollection." 1349 "ManagerAccountCollection"}, 1350 {"Name", "Accounts Collection"}, 1351 {"Description", "BMC User Accounts"}}; 1352 1353 crow::connections::systemBus->async_method_call( 1354 [asyncResp, &req, this](const boost::system::error_code ec, 1355 const ManagedObjectType& users) { 1356 if (ec) 1357 { 1358 messages::internalError(asyncResp->res); 1359 return; 1360 } 1361 1362 nlohmann::json& memberArray = 1363 asyncResp->res.jsonValue["Members"]; 1364 memberArray = nlohmann::json::array(); 1365 1366 for (auto& user : users) 1367 { 1368 const std::string& path = 1369 static_cast<const std::string&>(user.first); 1370 std::size_t lastIndex = path.rfind("/"); 1371 if (lastIndex == std::string::npos) 1372 { 1373 lastIndex = 0; 1374 } 1375 else 1376 { 1377 lastIndex += 1; 1378 } 1379 1380 // As clarified by Redfish here: 1381 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1382 // Users without ConfigureUsers, only see their own account. 1383 // Users with ConfigureUsers, see all accounts. 1384 if (req.session->username == path.substr(lastIndex) || 1385 isAllowedWithoutConfigureSelf(req)) 1386 { 1387 memberArray.push_back( 1388 {{"@odata.id", 1389 "/redfish/v1/AccountService/Accounts/" + 1390 path.substr(lastIndex)}}); 1391 } 1392 } 1393 asyncResp->res.jsonValue["Members@odata.count"] = 1394 memberArray.size(); 1395 }, 1396 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1397 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1398 } 1399 void doPost(crow::Response& res, const crow::Request& req, 1400 const std::vector<std::string>&) override 1401 { 1402 auto asyncResp = std::make_shared<AsyncResp>(res); 1403 1404 std::string username; 1405 std::string password; 1406 std::optional<std::string> roleId("User"); 1407 std::optional<bool> enabled = true; 1408 if (!json_util::readJson(req, res, "UserName", username, "Password", 1409 password, "RoleId", roleId, "Enabled", 1410 enabled)) 1411 { 1412 return; 1413 } 1414 1415 std::string priv = getPrivilegeFromRoleId(*roleId); 1416 if (priv.empty()) 1417 { 1418 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1419 return; 1420 } 1421 // TODO: Following override will be reverted once support in 1422 // phosphor-user-manager is added. In order to avoid dependency issues, 1423 // this is added in bmcweb, which will removed, once 1424 // phosphor-user-manager supports priv-noaccess. 1425 if (priv == "priv-noaccess") 1426 { 1427 roleId = ""; 1428 } 1429 else 1430 { 1431 roleId = priv; 1432 } 1433 1434 // Reading AllGroups property 1435 crow::connections::systemBus->async_method_call( 1436 [asyncResp, username, password{std::move(password)}, roleId, 1437 enabled](const boost::system::error_code ec, 1438 const std::variant<std::vector<std::string>>& allGroups) { 1439 if (ec) 1440 { 1441 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1442 messages::internalError(asyncResp->res); 1443 return; 1444 } 1445 1446 const std::vector<std::string>* allGroupsList = 1447 std::get_if<std::vector<std::string>>(&allGroups); 1448 1449 if (allGroupsList == nullptr || allGroupsList->empty()) 1450 { 1451 messages::internalError(asyncResp->res); 1452 return; 1453 } 1454 1455 crow::connections::systemBus->async_method_call( 1456 [asyncResp, username, password{std::move(password)}]( 1457 const boost::system::error_code ec2, 1458 sdbusplus::message::message& m) { 1459 if (ec2) 1460 { 1461 userErrorMessageHandler(m.get_error(), asyncResp, 1462 username, ""); 1463 return; 1464 } 1465 1466 if (pamUpdatePassword(username, password) != 1467 PAM_SUCCESS) 1468 { 1469 // At this point we have a user that's been created, 1470 // but the password set failed.Something is wrong, 1471 // so delete the user that we've already created 1472 crow::connections::systemBus->async_method_call( 1473 [asyncResp, password]( 1474 const boost::system::error_code ec3) { 1475 if (ec3) 1476 { 1477 messages::internalError(asyncResp->res); 1478 return; 1479 } 1480 1481 // If password is invalid 1482 messages::propertyValueFormatError( 1483 asyncResp->res, password, "Password"); 1484 }, 1485 "xyz.openbmc_project.User.Manager", 1486 "/xyz/openbmc_project/user/" + username, 1487 "xyz.openbmc_project.Object.Delete", "Delete"); 1488 1489 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1490 return; 1491 } 1492 1493 messages::created(asyncResp->res); 1494 asyncResp->res.addHeader( 1495 "Location", 1496 "/redfish/v1/AccountService/Accounts/" + username); 1497 }, 1498 "xyz.openbmc_project.User.Manager", 1499 "/xyz/openbmc_project/user", 1500 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1501 *allGroupsList, *roleId, *enabled); 1502 }, 1503 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1504 "org.freedesktop.DBus.Properties", "Get", 1505 "xyz.openbmc_project.User.Manager", "AllGroups"); 1506 } 1507 }; 1508 1509 class ManagerAccount : public Node 1510 { 1511 public: 1512 ManagerAccount(App& app) : 1513 Node(app, "/redfish/v1/AccountService/Accounts/<str>/", std::string()) 1514 { 1515 entityPrivileges = { 1516 {boost::beast::http::verb::get, 1517 {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, 1518 {boost::beast::http::verb::head, {{"Login"}}}, 1519 {boost::beast::http::verb::patch, 1520 {{"ConfigureUsers"}, {"ConfigureSelf"}}}, 1521 {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, 1522 {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, 1523 {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; 1524 } 1525 1526 private: 1527 void doGet(crow::Response& res, const crow::Request& req, 1528 const std::vector<std::string>& params) override 1529 { 1530 auto asyncResp = std::make_shared<AsyncResp>(res); 1531 1532 if (params.size() != 1) 1533 { 1534 messages::internalError(asyncResp->res); 1535 return; 1536 } 1537 1538 // Perform a proper ConfigureSelf authority check. If the 1539 // user is operating on an account not their own, then their 1540 // ConfigureSelf privilege does not apply. In this case, 1541 // perform the authority check again without the user's 1542 // ConfigureSelf privilege. 1543 if (req.session->username != params[0]) 1544 { 1545 if (!isAllowedWithoutConfigureSelf(req)) 1546 { 1547 BMCWEB_LOG_DEBUG << "GET Account denied access"; 1548 messages::insufficientPrivilege(asyncResp->res); 1549 return; 1550 } 1551 } 1552 1553 crow::connections::systemBus->async_method_call( 1554 [asyncResp, accountName{std::string(params[0])}]( 1555 const boost::system::error_code ec, 1556 const ManagedObjectType& users) { 1557 if (ec) 1558 { 1559 messages::internalError(asyncResp->res); 1560 return; 1561 } 1562 auto userIt = users.begin(); 1563 1564 for (; userIt != users.end(); userIt++) 1565 { 1566 if (boost::ends_with(userIt->first.str, "/" + accountName)) 1567 { 1568 break; 1569 } 1570 } 1571 if (userIt == users.end()) 1572 { 1573 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1574 accountName); 1575 return; 1576 } 1577 1578 asyncResp->res.jsonValue = { 1579 {"@odata.type", "#ManagerAccount.v1_4_0.ManagerAccount"}, 1580 {"Name", "User Account"}, 1581 {"Description", "User Account"}, 1582 {"Password", nullptr}, 1583 {"AccountTypes", {"Redfish"}}}; 1584 1585 for (const auto& interface : userIt->second) 1586 { 1587 if (interface.first == 1588 "xyz.openbmc_project.User.Attributes") 1589 { 1590 for (const auto& property : interface.second) 1591 { 1592 if (property.first == "UserEnabled") 1593 { 1594 const bool* userEnabled = 1595 std::get_if<bool>(&property.second); 1596 if (userEnabled == nullptr) 1597 { 1598 BMCWEB_LOG_ERROR 1599 << "UserEnabled wasn't a bool"; 1600 messages::internalError(asyncResp->res); 1601 return; 1602 } 1603 asyncResp->res.jsonValue["Enabled"] = 1604 *userEnabled; 1605 } 1606 else if (property.first == 1607 "UserLockedForFailedAttempt") 1608 { 1609 const bool* userLocked = 1610 std::get_if<bool>(&property.second); 1611 if (userLocked == nullptr) 1612 { 1613 BMCWEB_LOG_ERROR << "UserLockedForF" 1614 "ailedAttempt " 1615 "wasn't a bool"; 1616 messages::internalError(asyncResp->res); 1617 return; 1618 } 1619 asyncResp->res.jsonValue["Locked"] = 1620 *userLocked; 1621 asyncResp->res.jsonValue 1622 ["Locked@Redfish.AllowableValues"] = { 1623 "false"}; // can only unlock accounts 1624 } 1625 else if (property.first == "UserPrivilege") 1626 { 1627 const std::string* userPrivPtr = 1628 std::get_if<std::string>(&property.second); 1629 if (userPrivPtr == nullptr) 1630 { 1631 BMCWEB_LOG_ERROR 1632 << "UserPrivilege wasn't a " 1633 "string"; 1634 messages::internalError(asyncResp->res); 1635 return; 1636 } 1637 std::string role = 1638 getRoleIdFromPrivilege(*userPrivPtr); 1639 if (role.empty()) 1640 { 1641 BMCWEB_LOG_ERROR << "Invalid user role"; 1642 messages::internalError(asyncResp->res); 1643 return; 1644 } 1645 asyncResp->res.jsonValue["RoleId"] = role; 1646 1647 asyncResp->res.jsonValue["Links"]["Role"] = { 1648 {"@odata.id", "/redfish/v1/AccountService/" 1649 "Roles/" + 1650 role}}; 1651 } 1652 else if (property.first == "UserPasswordExpired") 1653 { 1654 const bool* userPasswordExpired = 1655 std::get_if<bool>(&property.second); 1656 if (userPasswordExpired == nullptr) 1657 { 1658 BMCWEB_LOG_ERROR << "UserPassword" 1659 "Expired " 1660 "wasn't a bool"; 1661 messages::internalError(asyncResp->res); 1662 return; 1663 } 1664 asyncResp->res 1665 .jsonValue["PasswordChangeRequired"] = 1666 *userPasswordExpired; 1667 } 1668 } 1669 } 1670 } 1671 1672 asyncResp->res.jsonValue["@odata.id"] = 1673 "/redfish/v1/AccountService/Accounts/" + accountName; 1674 asyncResp->res.jsonValue["Id"] = accountName; 1675 asyncResp->res.jsonValue["UserName"] = accountName; 1676 }, 1677 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1678 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1679 } 1680 1681 void doPatch(crow::Response& res, const crow::Request& req, 1682 const std::vector<std::string>& params) override 1683 { 1684 auto asyncResp = std::make_shared<AsyncResp>(res); 1685 if (params.size() != 1) 1686 { 1687 messages::internalError(asyncResp->res); 1688 return; 1689 } 1690 1691 std::optional<std::string> newUserName; 1692 std::optional<std::string> password; 1693 std::optional<bool> enabled; 1694 std::optional<std::string> roleId; 1695 std::optional<bool> locked; 1696 if (!json_util::readJson(req, res, "UserName", newUserName, "Password", 1697 password, "RoleId", roleId, "Enabled", enabled, 1698 "Locked", locked)) 1699 { 1700 return; 1701 } 1702 1703 const std::string& username = params[0]; 1704 1705 // Perform a proper ConfigureSelf authority check. If the 1706 // session is being used to PATCH a property other than 1707 // Password, then the ConfigureSelf privilege does not apply. 1708 // If the user is operating on an account not their own, then 1709 // their ConfigureSelf privilege does not apply. In either 1710 // case, perform the authority check again without the user's 1711 // ConfigureSelf privilege. 1712 if ((username != req.session->username) || 1713 (newUserName || enabled || roleId || locked)) 1714 { 1715 if (!isAllowedWithoutConfigureSelf(req)) 1716 { 1717 BMCWEB_LOG_WARNING << "PATCH Password denied access"; 1718 asyncResp->res.clear(); 1719 messages::insufficientPrivilege(asyncResp->res); 1720 return; 1721 } 1722 } 1723 1724 // if user name is not provided in the patch method or if it 1725 // matches the user name in the URI, then we are treating it as updating 1726 // user properties other then username. If username provided doesn't 1727 // match the URI, then we are treating this as user rename request. 1728 if (!newUserName || (newUserName.value() == username)) 1729 { 1730 updateUserProperties(asyncResp, username, password, enabled, roleId, 1731 locked); 1732 return; 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, newUser, 1743 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 void updateUserProperties(std::shared_ptr<AsyncResp> asyncResp, 1756 const std::string& username, 1757 std::optional<std::string> password, 1758 std::optional<bool> enabled, 1759 std::optional<std::string> roleId, 1760 std::optional<bool> locked) 1761 { 1762 std::string dbusObjectPath = "/xyz/openbmc_project/user/" + username; 1763 dbus::utility::escapePathForDbus(dbusObjectPath); 1764 1765 dbus::utility::checkDbusPathExists( 1766 dbusObjectPath, 1767 [dbusObjectPath(std::move(dbusObjectPath)), username, 1768 password(std::move(password)), roleId(std::move(roleId)), 1769 enabled(std::move(enabled)), locked(std::move(locked)), 1770 asyncResp{std::move(asyncResp)}](int rc) { 1771 if (!rc) 1772 { 1773 messages::resourceNotFound( 1774 asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", 1775 username); 1776 return; 1777 } 1778 1779 if (password) 1780 { 1781 int retval = pamUpdatePassword(username, *password); 1782 1783 if (retval == PAM_USER_UNKNOWN) 1784 { 1785 messages::resourceNotFound( 1786 asyncResp->res, 1787 "#ManagerAccount.v1_4_0.ManagerAccount", username); 1788 } 1789 else if (retval == PAM_AUTHTOK_ERR) 1790 { 1791 // If password is invalid 1792 messages::propertyValueFormatError( 1793 asyncResp->res, *password, "Password"); 1794 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1795 } 1796 else if (retval != PAM_SUCCESS) 1797 { 1798 messages::internalError(asyncResp->res); 1799 return; 1800 } 1801 } 1802 1803 if (enabled) 1804 { 1805 crow::connections::systemBus->async_method_call( 1806 [asyncResp](const boost::system::error_code ec) { 1807 if (ec) 1808 { 1809 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1810 << ec; 1811 messages::internalError(asyncResp->res); 1812 return; 1813 } 1814 messages::success(asyncResp->res); 1815 return; 1816 }, 1817 "xyz.openbmc_project.User.Manager", 1818 dbusObjectPath.c_str(), 1819 "org.freedesktop.DBus.Properties", "Set", 1820 "xyz.openbmc_project.User.Attributes", "UserEnabled", 1821 std::variant<bool>{*enabled}); 1822 } 1823 1824 if (roleId) 1825 { 1826 std::string priv = getPrivilegeFromRoleId(*roleId); 1827 if (priv.empty()) 1828 { 1829 messages::propertyValueNotInList(asyncResp->res, 1830 *roleId, "RoleId"); 1831 return; 1832 } 1833 if (priv == "priv-noaccess") 1834 { 1835 priv = ""; 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 }, 1849 "xyz.openbmc_project.User.Manager", 1850 dbusObjectPath.c_str(), 1851 "org.freedesktop.DBus.Properties", "Set", 1852 "xyz.openbmc_project.User.Attributes", "UserPrivilege", 1853 std::variant<std::string>{priv}); 1854 } 1855 1856 if (locked) 1857 { 1858 // admin can unlock the account which is locked by 1859 // successive authentication failures but admin should 1860 // not be allowed to lock an account. 1861 if (*locked) 1862 { 1863 messages::propertyValueNotInList(asyncResp->res, "true", 1864 "Locked"); 1865 return; 1866 } 1867 1868 crow::connections::systemBus->async_method_call( 1869 [asyncResp](const boost::system::error_code ec) { 1870 if (ec) 1871 { 1872 BMCWEB_LOG_ERROR << "D-Bus responses error: " 1873 << ec; 1874 messages::internalError(asyncResp->res); 1875 return; 1876 } 1877 messages::success(asyncResp->res); 1878 return; 1879 }, 1880 "xyz.openbmc_project.User.Manager", 1881 dbusObjectPath.c_str(), 1882 "org.freedesktop.DBus.Properties", "Set", 1883 "xyz.openbmc_project.User.Attributes", 1884 "UserLockedForFailedAttempt", 1885 std::variant<bool>{*locked}); 1886 } 1887 }); 1888 } 1889 1890 void doDelete(crow::Response& res, const crow::Request&, 1891 const std::vector<std::string>& params) override 1892 { 1893 auto asyncResp = std::make_shared<AsyncResp>(res); 1894 1895 if (params.size() != 1) 1896 { 1897 messages::internalError(asyncResp->res); 1898 return; 1899 } 1900 1901 const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; 1902 1903 crow::connections::systemBus->async_method_call( 1904 [asyncResp, username{std::move(params[0])}]( 1905 const boost::system::error_code ec) { 1906 if (ec) 1907 { 1908 messages::resourceNotFound( 1909 asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", 1910 username); 1911 return; 1912 } 1913 1914 messages::accountRemoved(asyncResp->res); 1915 }, 1916 "xyz.openbmc_project.User.Manager", userPath, 1917 "xyz.openbmc_project.Object.Delete", "Delete"); 1918 } 1919 }; 1920 1921 } // namespace redfish 1922