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