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