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