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