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