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