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