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