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