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