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 handleAccountServiceHead( 1242 App& app, const crow::Request& req, 1243 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1244 { 1245 1246 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1247 { 1248 return; 1249 } 1250 asyncResp->res.addHeader( 1251 boost::beast::http::field::link, 1252 "</redfish/v1/JsonSchemas/AccountService/AccountService.json>; rel=describedby"); 1253 } 1254 1255 inline void 1256 handleAccountServiceGet(App& app, const crow::Request& req, 1257 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1258 { 1259 handleAccountServiceHead(app, req, asyncResp); 1260 const persistent_data::AuthConfigMethods& authMethodsConfig = 1261 persistent_data::SessionStore::getInstance().getAuthMethodsConfig(); 1262 1263 nlohmann::json& json = asyncResp->res.jsonValue; 1264 json["@odata.id"] = "/redfish/v1/AccountService"; 1265 json["@odata.type"] = "#AccountService." 1266 "v1_10_0.AccountService"; 1267 json["Id"] = "AccountService"; 1268 json["Name"] = "Account Service"; 1269 json["Description"] = "Account Service"; 1270 json["ServiceEnabled"] = true; 1271 json["MaxPasswordLength"] = 20; 1272 json["Accounts"]["@odata.id"] = "/redfish/v1/AccountService/Accounts"; 1273 json["Roles"]["@odata.id"] = "/redfish/v1/AccountService/Roles"; 1274 json["Oem"]["OpenBMC"]["@odata.type"] = 1275 "#OemAccountService.v1_0_0.AccountService"; 1276 json["Oem"]["OpenBMC"]["@odata.id"] = 1277 "/redfish/v1/AccountService#/Oem/OpenBMC"; 1278 json["Oem"]["OpenBMC"]["AuthMethods"]["BasicAuth"] = 1279 authMethodsConfig.basic; 1280 json["Oem"]["OpenBMC"]["AuthMethods"]["SessionToken"] = 1281 authMethodsConfig.sessionToken; 1282 json["Oem"]["OpenBMC"]["AuthMethods"]["XToken"] = authMethodsConfig.xtoken; 1283 json["Oem"]["OpenBMC"]["AuthMethods"]["Cookie"] = authMethodsConfig.cookie; 1284 json["Oem"]["OpenBMC"]["AuthMethods"]["TLS"] = authMethodsConfig.tls; 1285 1286 // /redfish/v1/AccountService/LDAP/Certificates is something only 1287 // ConfigureManager can access then only display when the user has 1288 // permissions ConfigureManager 1289 Privileges effectiveUserPrivileges = 1290 redfish::getUserPrivileges(req.userRole); 1291 1292 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 1293 effectiveUserPrivileges)) 1294 { 1295 asyncResp->res.jsonValue["LDAP"]["Certificates"]["@odata.id"] = 1296 "/redfish/v1/AccountService/LDAP/Certificates"; 1297 } 1298 crow::connections::systemBus->async_method_call( 1299 [asyncResp](const boost::system::error_code ec, 1300 const dbus::utility::DBusPropertiesMap& propertiesList) { 1301 if (ec) 1302 { 1303 messages::internalError(asyncResp->res); 1304 return; 1305 } 1306 BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() 1307 << "properties for AccountService"; 1308 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 1309 property : propertiesList) 1310 { 1311 if (property.first == "MinPasswordLength") 1312 { 1313 const uint8_t* value = std::get_if<uint8_t>(&property.second); 1314 if (value != nullptr) 1315 { 1316 asyncResp->res.jsonValue["MinPasswordLength"] = *value; 1317 } 1318 } 1319 if (property.first == "AccountUnlockTimeout") 1320 { 1321 const uint32_t* value = std::get_if<uint32_t>(&property.second); 1322 if (value != nullptr) 1323 { 1324 asyncResp->res.jsonValue["AccountLockoutDuration"] = *value; 1325 } 1326 } 1327 if (property.first == "MaxLoginAttemptBeforeLockout") 1328 { 1329 const uint16_t* value = std::get_if<uint16_t>(&property.second); 1330 if (value != nullptr) 1331 { 1332 asyncResp->res.jsonValue["AccountLockoutThreshold"] = 1333 *value; 1334 } 1335 } 1336 } 1337 }, 1338 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1339 "org.freedesktop.DBus.Properties", "GetAll", 1340 "xyz.openbmc_project.User.AccountPolicy"); 1341 1342 auto callback = [asyncResp](bool success, const LDAPConfigData& confData, 1343 const std::string& ldapType) { 1344 if (!success) 1345 { 1346 return; 1347 } 1348 parseLDAPConfigData(asyncResp->res.jsonValue, confData, ldapType); 1349 }; 1350 1351 getLDAPConfigData("LDAP", callback); 1352 getLDAPConfigData("ActiveDirectory", callback); 1353 } 1354 1355 inline void handleAccountServicePatch( 1356 App& app, const crow::Request& req, 1357 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1358 { 1359 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1360 { 1361 return; 1362 } 1363 std::optional<uint32_t> unlockTimeout; 1364 std::optional<uint16_t> lockoutThreshold; 1365 std::optional<uint8_t> minPasswordLength; 1366 std::optional<uint16_t> maxPasswordLength; 1367 std::optional<nlohmann::json> ldapObject; 1368 std::optional<nlohmann::json> activeDirectoryObject; 1369 std::optional<nlohmann::json> oemObject; 1370 1371 if (!json_util::readJsonPatch( 1372 req, asyncResp->res, "AccountLockoutDuration", unlockTimeout, 1373 "AccountLockoutThreshold", lockoutThreshold, "MaxPasswordLength", 1374 maxPasswordLength, "MinPasswordLength", minPasswordLength, "LDAP", 1375 ldapObject, "ActiveDirectory", activeDirectoryObject, "Oem", 1376 oemObject)) 1377 { 1378 return; 1379 } 1380 1381 if (minPasswordLength) 1382 { 1383 crow::connections::systemBus->async_method_call( 1384 [asyncResp](const boost::system::error_code ec) { 1385 if (ec) 1386 { 1387 messages::internalError(asyncResp->res); 1388 return; 1389 } 1390 messages::success(asyncResp->res); 1391 }, 1392 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1393 "org.freedesktop.DBus.Properties", "Set", 1394 "xyz.openbmc_project.User.AccountPolicy", "MinPasswordLength", 1395 dbus::utility::DbusVariantType(*minPasswordLength)); 1396 } 1397 1398 if (maxPasswordLength) 1399 { 1400 messages::propertyNotWritable(asyncResp->res, "MaxPasswordLength"); 1401 } 1402 1403 if (ldapObject) 1404 { 1405 handleLDAPPatch(*ldapObject, asyncResp, "LDAP"); 1406 } 1407 1408 if (std::optional<nlohmann::json> oemOpenBMCObject; 1409 oemObject && json_util::readJson(*oemObject, asyncResp->res, "OpenBMC", 1410 oemOpenBMCObject)) 1411 { 1412 if (std::optional<nlohmann::json> authMethodsObject; 1413 oemOpenBMCObject && 1414 json_util::readJson(*oemOpenBMCObject, asyncResp->res, 1415 "AuthMethods", authMethodsObject)) 1416 { 1417 if (authMethodsObject) 1418 { 1419 handleAuthMethodsPatch(*authMethodsObject, asyncResp); 1420 } 1421 } 1422 } 1423 1424 if (activeDirectoryObject) 1425 { 1426 handleLDAPPatch(*activeDirectoryObject, asyncResp, "ActiveDirectory"); 1427 } 1428 1429 if (unlockTimeout) 1430 { 1431 crow::connections::systemBus->async_method_call( 1432 [asyncResp](const boost::system::error_code ec) { 1433 if (ec) 1434 { 1435 messages::internalError(asyncResp->res); 1436 return; 1437 } 1438 messages::success(asyncResp->res); 1439 }, 1440 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1441 "org.freedesktop.DBus.Properties", "Set", 1442 "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", 1443 dbus::utility::DbusVariantType(*unlockTimeout)); 1444 } 1445 if (lockoutThreshold) 1446 { 1447 crow::connections::systemBus->async_method_call( 1448 [asyncResp](const boost::system::error_code ec) { 1449 if (ec) 1450 { 1451 messages::internalError(asyncResp->res); 1452 return; 1453 } 1454 messages::success(asyncResp->res); 1455 }, 1456 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1457 "org.freedesktop.DBus.Properties", "Set", 1458 "xyz.openbmc_project.User.AccountPolicy", 1459 "MaxLoginAttemptBeforeLockout", 1460 dbus::utility::DbusVariantType(*lockoutThreshold)); 1461 } 1462 } 1463 1464 inline void handleAccountCollectionHead( 1465 App& app, const crow::Request& req, 1466 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1467 { 1468 1469 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1470 { 1471 return; 1472 } 1473 asyncResp->res.addHeader( 1474 boost::beast::http::field::link, 1475 "</redfish/v1/JsonSchemas/ManagerAccountCollection.json>; rel=describedby"); 1476 } 1477 1478 inline void handleAccountCollectionGet( 1479 App& app, const crow::Request& req, 1480 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1481 { 1482 handleAccountCollectionHead(app, req, asyncResp); 1483 1484 asyncResp->res.jsonValue["@odata.id"] = 1485 "/redfish/v1/AccountService/Accounts"; 1486 asyncResp->res.jsonValue["@odata.type"] = "#ManagerAccountCollection." 1487 "ManagerAccountCollection"; 1488 asyncResp->res.jsonValue["Name"] = "Accounts Collection"; 1489 asyncResp->res.jsonValue["Description"] = "BMC User Accounts"; 1490 1491 Privileges effectiveUserPrivileges = 1492 redfish::getUserPrivileges(req.userRole); 1493 1494 std::string thisUser; 1495 if (req.session) 1496 { 1497 thisUser = req.session->username; 1498 } 1499 crow::connections::systemBus->async_method_call( 1500 [asyncResp, thisUser, effectiveUserPrivileges]( 1501 const boost::system::error_code ec, 1502 const dbus::utility::ManagedObjectType& users) { 1503 if (ec) 1504 { 1505 messages::internalError(asyncResp->res); 1506 return; 1507 } 1508 1509 bool userCanSeeAllAccounts = 1510 effectiveUserPrivileges.isSupersetOf({"ConfigureUsers"}); 1511 1512 bool userCanSeeSelf = 1513 effectiveUserPrivileges.isSupersetOf({"ConfigureSelf"}); 1514 1515 nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; 1516 memberArray = nlohmann::json::array(); 1517 1518 for (const auto& userpath : users) 1519 { 1520 std::string user = userpath.first.filename(); 1521 if (user.empty()) 1522 { 1523 messages::internalError(asyncResp->res); 1524 BMCWEB_LOG_ERROR << "Invalid firmware ID"; 1525 1526 return; 1527 } 1528 1529 // As clarified by Redfish here: 1530 // https://redfishforum.com/thread/281/manageraccountcollection-change-allows-account-enumeration 1531 // Users without ConfigureUsers, only see their own 1532 // account. Users with ConfigureUsers, see all 1533 // accounts. 1534 if (userCanSeeAllAccounts || (thisUser == user && userCanSeeSelf)) 1535 { 1536 nlohmann::json::object_t member; 1537 member["@odata.id"] = 1538 "/redfish/v1/AccountService/Accounts/" + user; 1539 memberArray.push_back(std::move(member)); 1540 } 1541 } 1542 asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); 1543 }, 1544 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1545 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1546 } 1547 1548 inline void handleAccountCollectionPost( 1549 App& app, const crow::Request& req, 1550 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1551 { 1552 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1553 { 1554 return; 1555 } 1556 std::string username; 1557 std::string password; 1558 std::optional<std::string> roleId("User"); 1559 std::optional<bool> enabled = true; 1560 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", username, 1561 "Password", password, "RoleId", roleId, 1562 "Enabled", enabled)) 1563 { 1564 return; 1565 } 1566 1567 std::string priv = getPrivilegeFromRoleId(*roleId); 1568 if (priv.empty()) 1569 { 1570 messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); 1571 return; 1572 } 1573 // TODO: Following override will be reverted once support in 1574 // phosphor-user-manager is added. In order to avoid dependency 1575 // issues, this is added in bmcweb, which will removed, once 1576 // phosphor-user-manager supports priv-noaccess. 1577 if (priv == "priv-noaccess") 1578 { 1579 roleId = ""; 1580 } 1581 else 1582 { 1583 roleId = priv; 1584 } 1585 1586 // Reading AllGroups property 1587 sdbusplus::asio::getProperty<std::vector<std::string>>( 1588 *crow::connections::systemBus, "xyz.openbmc_project.User.Manager", 1589 "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", 1590 "AllGroups", 1591 [asyncResp, username, password{std::move(password)}, roleId, 1592 enabled](const boost::system::error_code ec, 1593 const std::vector<std::string>& allGroupsList) { 1594 if (ec) 1595 { 1596 BMCWEB_LOG_DEBUG << "ERROR with async_method_call"; 1597 messages::internalError(asyncResp->res); 1598 return; 1599 } 1600 1601 if (allGroupsList.empty()) 1602 { 1603 messages::internalError(asyncResp->res); 1604 return; 1605 } 1606 1607 crow::connections::systemBus->async_method_call( 1608 [asyncResp, username, password](const boost::system::error_code ec2, 1609 sdbusplus::message_t& m) { 1610 if (ec2) 1611 { 1612 userErrorMessageHandler(m.get_error(), asyncResp, username, ""); 1613 return; 1614 } 1615 1616 if (pamUpdatePassword(username, password) != PAM_SUCCESS) 1617 { 1618 // At this point we have a user that's been 1619 // created, but the password set 1620 // failed.Something is wrong, so delete the user 1621 // that we've already created 1622 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1623 tempObjPath /= username; 1624 const std::string userPath(tempObjPath); 1625 1626 crow::connections::systemBus->async_method_call( 1627 [asyncResp, password](const boost::system::error_code ec3) { 1628 if (ec3) 1629 { 1630 messages::internalError(asyncResp->res); 1631 return; 1632 } 1633 1634 // If password is invalid 1635 messages::propertyValueFormatError(asyncResp->res, password, 1636 "Password"); 1637 }, 1638 "xyz.openbmc_project.User.Manager", userPath, 1639 "xyz.openbmc_project.Object.Delete", "Delete"); 1640 1641 BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; 1642 return; 1643 } 1644 1645 messages::created(asyncResp->res); 1646 asyncResp->res.addHeader( 1647 "Location", "/redfish/v1/AccountService/Accounts/" + username); 1648 }, 1649 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1650 "xyz.openbmc_project.User.Manager", "CreateUser", username, 1651 allGroupsList, *roleId, *enabled); 1652 }); 1653 } 1654 1655 inline void 1656 handleAccountHead(App& app, const crow::Request& req, 1657 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1658 const std::string& /*accountName*/) 1659 { 1660 1661 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1662 { 1663 return; 1664 } 1665 asyncResp->res.addHeader( 1666 boost::beast::http::field::link, 1667 "</redfish/v1/JsonSchemas/ManagerAccount/ManagerAccount.json>; rel=describedby"); 1668 } 1669 inline void 1670 handleAccountGet(App& app, const crow::Request& req, 1671 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1672 const std::string& accountName) 1673 { 1674 handleAccountHead(app, req, asyncResp, accountName); 1675 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1676 // If authentication is disabled, there are no user accounts 1677 messages::resourceNotFound( 1678 asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", accountName); 1679 return; 1680 1681 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1682 if (req.session == nullptr) 1683 { 1684 messages::internalError(asyncResp->res); 1685 return; 1686 } 1687 if (req.session->username != accountName) 1688 { 1689 // At this point we've determined that the user is trying to 1690 // modify a user that isn't them. We need to verify that they 1691 // have permissions to modify other users, so re-run the auth 1692 // check with the same permissions, minus ConfigureSelf. 1693 Privileges effectiveUserPrivileges = 1694 redfish::getUserPrivileges(req.userRole); 1695 Privileges requiredPermissionsToChangeNonSelf = {"ConfigureUsers", 1696 "ConfigureManager"}; 1697 if (!effectiveUserPrivileges.isSupersetOf( 1698 requiredPermissionsToChangeNonSelf)) 1699 { 1700 BMCWEB_LOG_DEBUG << "GET Account denied access"; 1701 messages::insufficientPrivilege(asyncResp->res); 1702 return; 1703 } 1704 } 1705 1706 crow::connections::systemBus->async_method_call( 1707 [asyncResp, 1708 accountName](const boost::system::error_code ec, 1709 const dbus::utility::ManagedObjectType& users) { 1710 if (ec) 1711 { 1712 messages::internalError(asyncResp->res); 1713 return; 1714 } 1715 const auto userIt = std::find_if( 1716 users.begin(), users.end(), 1717 [accountName]( 1718 const std::pair<sdbusplus::message::object_path, 1719 dbus::utility::DBusInteracesMap>& user) { 1720 return accountName == user.first.filename(); 1721 }); 1722 1723 if (userIt == users.end()) 1724 { 1725 messages::resourceNotFound(asyncResp->res, "ManagerAccount", 1726 accountName); 1727 return; 1728 } 1729 1730 asyncResp->res.jsonValue["@odata.type"] = 1731 "#ManagerAccount.v1_4_0.ManagerAccount"; 1732 asyncResp->res.jsonValue["Name"] = "User Account"; 1733 asyncResp->res.jsonValue["Description"] = "User Account"; 1734 asyncResp->res.jsonValue["Password"] = nullptr; 1735 asyncResp->res.jsonValue["AccountTypes"] = {"Redfish"}; 1736 1737 for (const auto& interface : userIt->second) 1738 { 1739 if (interface.first == "xyz.openbmc_project.User.Attributes") 1740 { 1741 for (const auto& property : interface.second) 1742 { 1743 if (property.first == "UserEnabled") 1744 { 1745 const bool* userEnabled = 1746 std::get_if<bool>(&property.second); 1747 if (userEnabled == nullptr) 1748 { 1749 BMCWEB_LOG_ERROR << "UserEnabled wasn't a bool"; 1750 messages::internalError(asyncResp->res); 1751 return; 1752 } 1753 asyncResp->res.jsonValue["Enabled"] = *userEnabled; 1754 } 1755 else if (property.first == "UserLockedForFailedAttempt") 1756 { 1757 const bool* userLocked = 1758 std::get_if<bool>(&property.second); 1759 if (userLocked == nullptr) 1760 { 1761 BMCWEB_LOG_ERROR << "UserLockedForF" 1762 "ailedAttempt " 1763 "wasn't a bool"; 1764 messages::internalError(asyncResp->res); 1765 return; 1766 } 1767 asyncResp->res.jsonValue["Locked"] = *userLocked; 1768 asyncResp->res 1769 .jsonValue["Locked@Redfish.AllowableValues"] = { 1770 "false"}; // can only unlock accounts 1771 } 1772 else if (property.first == "UserPrivilege") 1773 { 1774 const std::string* userPrivPtr = 1775 std::get_if<std::string>(&property.second); 1776 if (userPrivPtr == nullptr) 1777 { 1778 BMCWEB_LOG_ERROR << "UserPrivilege wasn't a " 1779 "string"; 1780 messages::internalError(asyncResp->res); 1781 return; 1782 } 1783 std::string role = getRoleIdFromPrivilege(*userPrivPtr); 1784 if (role.empty()) 1785 { 1786 BMCWEB_LOG_ERROR << "Invalid user role"; 1787 messages::internalError(asyncResp->res); 1788 return; 1789 } 1790 asyncResp->res.jsonValue["RoleId"] = role; 1791 1792 nlohmann::json& roleEntry = 1793 asyncResp->res.jsonValue["Links"]["Role"]; 1794 roleEntry["@odata.id"] = 1795 "/redfish/v1/AccountService/Roles/" + role; 1796 } 1797 else if (property.first == "UserPasswordExpired") 1798 { 1799 const bool* userPasswordExpired = 1800 std::get_if<bool>(&property.second); 1801 if (userPasswordExpired == nullptr) 1802 { 1803 BMCWEB_LOG_ERROR 1804 << "UserPasswordExpired wasn't a bool"; 1805 messages::internalError(asyncResp->res); 1806 return; 1807 } 1808 asyncResp->res.jsonValue["PasswordChangeRequired"] = 1809 *userPasswordExpired; 1810 } 1811 } 1812 } 1813 } 1814 1815 asyncResp->res.jsonValue["@odata.id"] = 1816 "/redfish/v1/AccountService/Accounts/" + accountName; 1817 asyncResp->res.jsonValue["Id"] = accountName; 1818 asyncResp->res.jsonValue["UserName"] = accountName; 1819 }, 1820 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1821 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 1822 } 1823 1824 inline void 1825 handleAccounttDelete(App& app, const crow::Request& req, 1826 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1827 const std::string& username) 1828 { 1829 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1830 { 1831 return; 1832 } 1833 1834 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1835 // If authentication is disabled, there are no user accounts 1836 messages::resourceNotFound( 1837 asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", username); 1838 return; 1839 1840 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1841 sdbusplus::message::object_path tempObjPath(rootUserDbusPath); 1842 tempObjPath /= username; 1843 const std::string userPath(tempObjPath); 1844 1845 crow::connections::systemBus->async_method_call( 1846 [asyncResp, username](const boost::system::error_code ec) { 1847 if (ec) 1848 { 1849 messages::resourceNotFound(asyncResp->res, 1850 "#ManagerAccount.v1_4_0.ManagerAccount", 1851 username); 1852 return; 1853 } 1854 1855 messages::accountRemoved(asyncResp->res); 1856 }, 1857 "xyz.openbmc_project.User.Manager", userPath, 1858 "xyz.openbmc_project.Object.Delete", "Delete"); 1859 } 1860 1861 inline void 1862 handleAccountPatch(App& app, const crow::Request& req, 1863 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1864 const std::string& username) 1865 { 1866 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1867 { 1868 return; 1869 } 1870 #ifdef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1871 // If authentication is disabled, there are no user accounts 1872 messages::resourceNotFound( 1873 asyncResp->res, "#ManagerAccount.v1_4_0.ManagerAccount", username); 1874 return; 1875 1876 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 1877 std::optional<std::string> newUserName; 1878 std::optional<std::string> password; 1879 std::optional<bool> enabled; 1880 std::optional<std::string> roleId; 1881 std::optional<bool> locked; 1882 1883 if (req.session == nullptr) 1884 { 1885 messages::internalError(asyncResp->res); 1886 return; 1887 } 1888 1889 Privileges effectiveUserPrivileges = 1890 redfish::getUserPrivileges(req.userRole); 1891 Privileges configureUsers = {"ConfigureUsers"}; 1892 bool userHasConfigureUsers = 1893 effectiveUserPrivileges.isSupersetOf(configureUsers); 1894 if (userHasConfigureUsers) 1895 { 1896 // Users with ConfigureUsers can modify for all users 1897 if (!json_util::readJsonPatch(req, asyncResp->res, "UserName", 1898 newUserName, "Password", password, 1899 "RoleId", roleId, "Enabled", enabled, 1900 "Locked", locked)) 1901 { 1902 return; 1903 } 1904 } 1905 else 1906 { 1907 // ConfigureSelf accounts can only modify their own account 1908 if (username != req.session->username) 1909 { 1910 messages::insufficientPrivilege(asyncResp->res); 1911 return; 1912 } 1913 1914 // ConfigureSelf accounts can only modify their password 1915 if (!json_util::readJsonPatch(req, asyncResp->res, "Password", 1916 password)) 1917 { 1918 return; 1919 } 1920 } 1921 1922 // if user name is not provided in the patch method or if it 1923 // matches the user name in the URI, then we are treating it as 1924 // updating user properties other then username. If username 1925 // provided doesn't match the URI, then we are treating this as 1926 // user rename request. 1927 if (!newUserName || (newUserName.value() == username)) 1928 { 1929 updateUserProperties(asyncResp, username, password, enabled, roleId, 1930 locked); 1931 return; 1932 } 1933 crow::connections::systemBus->async_method_call( 1934 [asyncResp, username, password(std::move(password)), 1935 roleId(std::move(roleId)), enabled, newUser{std::string(*newUserName)}, 1936 locked](const boost::system::error_code ec, sdbusplus::message_t& m) { 1937 if (ec) 1938 { 1939 userErrorMessageHandler(m.get_error(), asyncResp, newUser, 1940 username); 1941 return; 1942 } 1943 1944 updateUserProperties(asyncResp, newUser, password, enabled, roleId, 1945 locked); 1946 }, 1947 "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", 1948 "xyz.openbmc_project.User.Manager", "RenameUser", username, 1949 *newUserName); 1950 } 1951 1952 inline void requestAccountServiceRoutes(App& app) 1953 { 1954 1955 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 1956 .privileges(redfish::privileges::headAccountService) 1957 .methods(boost::beast::http::verb::head)( 1958 std::bind_front(handleAccountServiceHead, std::ref(app))); 1959 1960 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 1961 .privileges(redfish::privileges::getAccountService) 1962 .methods(boost::beast::http::verb::get)( 1963 std::bind_front(handleAccountServiceGet, std::ref(app))); 1964 1965 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/") 1966 .privileges(redfish::privileges::patchAccountService) 1967 .methods(boost::beast::http::verb::patch)( 1968 std::bind_front(handleAccountServicePatch, std::ref(app))); 1969 1970 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 1971 .privileges(redfish::privileges::headManagerAccountCollection) 1972 .methods(boost::beast::http::verb::head)( 1973 std::bind_front(handleAccountCollectionHead, std::ref(app))); 1974 1975 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 1976 .privileges(redfish::privileges::getManagerAccountCollection) 1977 .methods(boost::beast::http::verb::get)( 1978 std::bind_front(handleAccountCollectionGet, std::ref(app))); 1979 1980 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/") 1981 .privileges(redfish::privileges::postManagerAccountCollection) 1982 .methods(boost::beast::http::verb::post)( 1983 std::bind_front(handleAccountCollectionPost, std::ref(app))); 1984 1985 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 1986 .privileges(redfish::privileges::headManagerAccount) 1987 .methods(boost::beast::http::verb::head)( 1988 std::bind_front(handleAccountHead, std::ref(app))); 1989 1990 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 1991 .privileges(redfish::privileges::getManagerAccount) 1992 .methods(boost::beast::http::verb::get)( 1993 std::bind_front(handleAccountGet, std::ref(app))); 1994 1995 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 1996 // TODO this privilege should be using the generated endpoints, but 1997 // because of the special handling of ConfigureSelf, it's not able to 1998 // yet 1999 .privileges({{"ConfigureUsers"}, {"ConfigureSelf"}}) 2000 .methods(boost::beast::http::verb::patch)( 2001 std::bind_front(handleAccountPatch, std::ref(app))); 2002 2003 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/Accounts/<str>/") 2004 .privileges(redfish::privileges::deleteManagerAccount) 2005 .methods(boost::beast::http::verb::delete_)( 2006 std::bind_front(handleAccounttDelete, std::ref(app))); 2007 } 2008 2009 } // namespace redfish 2010