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