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