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