/* // Copyright (c) 2018 Intel Corporation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. */ #pragma once #include "node.hpp" #include #include #include namespace redfish { using ManagedObjectType = std::vector>>>>; inline std::string getPrivilegeFromRoleId(boost::beast::string_view role) { if (role == "priv-admin") { return "Administrator"; } else if (role == "priv-callback") { return "Callback"; } else if (role == "priv-user") { return "User"; } else if (role == "priv-operator") { return "Operator"; } return ""; } inline std::string getRoleIdFromPrivilege(boost::beast::string_view role) { if (role == "Administrator") { return "priv-admin"; } else if (role == "Callback") { return "priv-callback"; } else if (role == "User") { return "priv-user"; } else if (role == "Operator") { return "priv-operator"; } return ""; } class AccountService : public Node { public: AccountService(CrowApp& app) : Node(app, "/redfish/v1/AccountService/") { entityPrivileges = { {boost::beast::http::verb::get, {{"ConfigureUsers"}, {"ConfigureManager"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; } private: void doGet(crow::Response& res, const crow::Request& req, const std::vector& params) override { auto asyncResp = std::make_shared(res); res.jsonValue = { {"@odata.context", "/redfish/v1/" "$metadata#AccountService.AccountService"}, {"@odata.id", "/redfish/v1/AccountService"}, {"@odata.type", "#AccountService." "v1_1_0.AccountService"}, {"Id", "AccountService"}, {"Name", "Account Service"}, {"Description", "Account Service"}, {"ServiceEnabled", true}, {"MaxPasswordLength", 31}, {"Accounts", {{"@odata.id", "/redfish/v1/AccountService/Accounts"}}}, {"Roles", {{"@odata.id", "/redfish/v1/AccountService/Roles"}}}}; crow::connections::systemBus->async_method_call( [asyncResp]( const boost::system::error_code ec, const std::vector>>& propertiesList) { if (ec) { messages::internalError(asyncResp->res); return; } BMCWEB_LOG_DEBUG << "Got " << propertiesList.size() << "properties for AccountService"; for (const std::pair>& property : propertiesList) { if (property.first == "MinPasswordLength") { const uint8_t* value = sdbusplus::message::variant_ns::get_if( &property.second); if (value != nullptr) { asyncResp->res.jsonValue["MinPasswordLength"] = *value; } } if (property.first == "AccountUnlockTimeout") { const uint32_t* value = sdbusplus::message::variant_ns::get_if( &property.second); if (value != nullptr) { asyncResp->res.jsonValue["AccountLockoutDuration"] = *value; } } if (property.first == "MaxLoginAttemptBeforeLockout") { const uint16_t* value = sdbusplus::message::variant_ns::get_if( &property.second); if (value != nullptr) { asyncResp->res .jsonValue["AccountLockoutThreshold"] = *value; } } } }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "org.freedesktop.DBus.Properties", "GetAll", "xyz.openbmc_project.User.AccountPolicy"); } void doPatch(crow::Response& res, const crow::Request& req, const std::vector& params) override { auto asyncResp = std::make_shared(res); std::optional unlockTimeout; std::optional lockoutThreshold; if (!json_util::readJson(req, res, "AccountLockoutDuration", unlockTimeout, "AccountLockoutThreshold", lockoutThreshold)) { return; } if (unlockTimeout) { crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec) { if (ec) { messages::internalError(asyncResp->res); return; } }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.User.AccountPolicy", "AccountUnlockTimeout", sdbusplus::message::variant(*unlockTimeout)); } if (lockoutThreshold) { crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec) { if (ec) { messages::internalError(asyncResp->res); return; } }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.User.AccountPolicy", "MaxLoginAttemptBeforeLockout", sdbusplus::message::variant(*lockoutThreshold)); } } }; class AccountsCollection : public Node { public: AccountsCollection(CrowApp& app) : Node(app, "/redfish/v1/AccountService/Accounts/") { entityPrivileges = { {boost::beast::http::verb::get, {{"ConfigureUsers"}, {"ConfigureManager"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; } private: void doGet(crow::Response& res, const crow::Request& req, const std::vector& params) override { auto asyncResp = std::make_shared(res); res.jsonValue = {{"@odata.context", "/redfish/v1/" "$metadata#ManagerAccountCollection." "ManagerAccountCollection"}, {"@odata.id", "/redfish/v1/AccountService/Accounts"}, {"@odata.type", "#ManagerAccountCollection." "ManagerAccountCollection"}, {"Name", "Accounts Collection"}, {"Description", "BMC User Accounts"}}; crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec, const ManagedObjectType& users) { if (ec) { messages::internalError(asyncResp->res); return; } nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; memberArray = nlohmann::json::array(); asyncResp->res.jsonValue["Members@odata.count"] = users.size(); for (auto& user : users) { const std::string& path = static_cast(user.first); std::size_t lastIndex = path.rfind("/"); if (lastIndex == std::string::npos) { lastIndex = 0; } else { lastIndex += 1; } memberArray.push_back( {{"@odata.id", "/redfish/v1/AccountService/Accounts/" + path.substr(lastIndex)}}); } }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); } void doPost(crow::Response& res, const crow::Request& req, const std::vector& params) override { auto asyncResp = std::make_shared(res); std::string username; std::string password; std::optional roleId("User"); std::optional enabled = true; if (!json_util::readJson(req, res, "UserName", username, "Password", password, "RoleId", roleId, "Enabled", enabled)) { return; } std::string priv = getRoleIdFromPrivilege(*roleId); if (priv.empty()) { messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); return; } roleId = priv; crow::connections::systemBus->async_method_call( [asyncResp, username, password{std::move(password)}]( const boost::system::error_code ec) { if (ec) { messages::resourceAlreadyExists( asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", "UserName", username); return; } if (!pamUpdatePassword(username, password)) { // At this point we have a user that's been created, but the // password set failed. Something is wrong, so delete the // user that we've already created crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec) { if (ec) { messages::internalError(asyncResp->res); return; } messages::invalidObject(asyncResp->res, "Password"); }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user/" + username, "xyz.openbmc_project.Object.Delete", "Delete"); BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; return; } messages::created(asyncResp->res); asyncResp->res.addHeader( "Location", "/redfish/v1/AccountService/Accounts/" + username); }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", "CreateUser", username, std::array{"ipmi", "redfish", "ssh", "web"}, *roleId, *enabled); } }; template inline void checkDbusPathExists(const std::string& path, Callback&& callback) { using GetObjectType = std::vector>>; crow::connections::systemBus->async_method_call( [callback{std::move(callback)}](const boost::system::error_code ec, const GetObjectType& object_names) { callback(!ec && object_names.size() != 0); }, "xyz.openbmc_project.ObjectMapper", "/xyz/openbmc_project/object_mapper", "xyz.openbmc_project.ObjectMapper", "GetObject", path, std::array()); } class ManagerAccount : public Node { public: ManagerAccount(CrowApp& app) : Node(app, "/redfish/v1/AccountService/Accounts//", std::string()) { entityPrivileges = { {boost::beast::http::verb::get, {{"ConfigureUsers"}, {"ConfigureManager"}, {"ConfigureSelf"}}}, {boost::beast::http::verb::head, {{"Login"}}}, {boost::beast::http::verb::patch, {{"ConfigureUsers"}}}, {boost::beast::http::verb::put, {{"ConfigureUsers"}}}, {boost::beast::http::verb::delete_, {{"ConfigureUsers"}}}, {boost::beast::http::verb::post, {{"ConfigureUsers"}}}}; } private: void doGet(crow::Response& res, const crow::Request& req, const std::vector& params) override { res.jsonValue = { {"@odata.context", "/redfish/v1/$metadata#ManagerAccount.ManagerAccount"}, {"@odata.type", "#ManagerAccount.v1_0_3.ManagerAccount"}, {"Name", "User Account"}, {"Description", "User Account"}, {"Password", nullptr}, {"RoleId", "Administrator"}}; auto asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } crow::connections::systemBus->async_method_call( [asyncResp, accountName{std::string(params[0])}]( const boost::system::error_code ec, const ManagedObjectType& users) { if (ec) { messages::internalError(asyncResp->res); return; } auto userIt = users.begin(); for (; userIt != users.end(); userIt++) { if (boost::ends_with(userIt->first.str, "/" + accountName)) { break; } } if (userIt == users.end()) { messages::resourceNotFound(asyncResp->res, "ManagerAccount", accountName); return; } for (const auto& interface : userIt->second) { if (interface.first == "xyz.openbmc_project.User.Attributes") { for (const auto& property : interface.second) { if (property.first == "UserEnabled") { const bool* userEnabled = sdbusplus::message::variant_ns::get_if< bool>(&property.second); if (userEnabled == nullptr) { BMCWEB_LOG_ERROR << "UserEnabled wasn't a bool"; messages::internalError(asyncResp->res); return; } asyncResp->res.jsonValue["Enabled"] = *userEnabled; } else if (property.first == "UserLockedForFailedAttempt") { const bool* userLocked = sdbusplus::message::variant_ns::get_if< bool>(&property.second); if (userLocked == nullptr) { BMCWEB_LOG_ERROR << "UserLockedForF" "ailedAttempt " "wasn't a bool"; messages::internalError(asyncResp->res); return; } asyncResp->res.jsonValue["Locked"] = *userLocked; } else if (property.first == "UserPrivilege") { const std::string* userRolePtr = sdbusplus::message::variant_ns::get_if< std::string>(&property.second); if (userRolePtr == nullptr) { BMCWEB_LOG_ERROR << "UserPrivilege wasn't a " "string"; messages::internalError(asyncResp->res); return; } std::string priv = getPrivilegeFromRoleId(*userRolePtr); if (priv.empty()) { BMCWEB_LOG_ERROR << "Invalid user role"; messages::internalError(asyncResp->res); return; } asyncResp->res.jsonValue["RoleId"] = priv; asyncResp->res.jsonValue["Links"]["Role"] = { {"@odata.id", "/redfish/v1/AccountService/" "Roles/" + priv}}; } } } } asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/AccountService/Accounts/" + accountName; asyncResp->res.jsonValue["Id"] = accountName; asyncResp->res.jsonValue["UserName"] = accountName; }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); } void doPatch(crow::Response& res, const crow::Request& req, const std::vector& params) override { auto asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } std::optional newUserName; std::optional password; std::optional enabled; std::optional roleId; if (!json_util::readJson(req, res, "UserName", newUserName, "Password", password, "RoleId", roleId, "Enabled", enabled)) { return; } const std::string& username = params[0]; if (!newUserName) { // If the username isn't being updated, we can update the properties // directly updateUserProperties(asyncResp, username, password, enabled, roleId); return; } else { crow::connections::systemBus->async_method_call( [this, asyncResp, username, password(std::move(password)), roleId(std::move(roleId)), enabled(std::move(enabled)), newUser{std::string(*newUserName)}]( const boost::system::error_code ec) { if (ec) { BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; messages::resourceNotFound( asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", username); return; } updateUserProperties(asyncResp, newUser, password, enabled, roleId); }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", "xyz.openbmc_project.User.Manager", "RenameUser", username, *newUserName); } } void updateUserProperties(std::shared_ptr asyncResp, const std::string& username, std::optional password, std::optional enabled, std::optional roleId) { if (password) { if (!pamUpdatePassword(username, *password)) { BMCWEB_LOG_ERROR << "pamUpdatePassword Failed"; messages::internalError(asyncResp->res); return; } } if (enabled) { crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec) { if (ec) { BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; messages::internalError(asyncResp->res); return; } messages::success(asyncResp->res); return; }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user/" + username, "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.User.Attributes", "UserEnabled", sdbusplus::message::variant{*enabled}); } if (roleId) { std::string priv = getRoleIdFromPrivilege(*roleId); if (priv.empty()) { messages::propertyValueNotInList(asyncResp->res, *roleId, "RoleId"); return; } crow::connections::systemBus->async_method_call( [asyncResp](const boost::system::error_code ec) { if (ec) { BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; messages::internalError(asyncResp->res); return; } messages::success(asyncResp->res); }, "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user/" + username, "org.freedesktop.DBus.Properties", "Set", "xyz.openbmc_project.User.Attributes", "UserPrivilege", sdbusplus::message::variant{priv}); } } void doDelete(crow::Response& res, const crow::Request& req, const std::vector& params) override { auto asyncResp = std::make_shared(res); if (params.size() != 1) { messages::internalError(asyncResp->res); return; } const std::string userPath = "/xyz/openbmc_project/user/" + params[0]; crow::connections::systemBus->async_method_call( [asyncResp, username{std::move(params[0])}]( const boost::system::error_code ec) { if (ec) { messages::resourceNotFound( asyncResp->res, "#ManagerAccount.v1_0_3.ManagerAccount", username); return; } messages::accountRemoved(asyncResp->res); }, "xyz.openbmc_project.User.Manager", userPath, "xyz.openbmc_project.Object.Delete", "Delete"); } }; } // namespace redfish