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