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