diff options
-rw-r--r-- | http/http_request.hpp | 1 | ||||
-rw-r--r-- | include/dbus_privileges.hpp | 134 | ||||
-rw-r--r-- | include/sessions.hpp | 8 | ||||
-rw-r--r-- | include/user_role_map.hpp | 278 | ||||
-rw-r--r-- | src/webserver_main.cpp | 4 |
5 files changed, 309 insertions, 116 deletions
diff --git a/http/http_request.hpp b/http/http_request.hpp index 5ce434b921..4762a9bb26 100644 --- a/http/http_request.hpp +++ b/http/http_request.hpp @@ -32,7 +32,6 @@ struct Request std::shared_ptr<persistent_data::UserSession> session; - std::string userRole{}; Request(boost::beast::http::request<boost::beast::http::string_body> reqIn, std::error_code& ec) : req(std::move(reqIn)) diff --git a/include/dbus_privileges.hpp b/include/dbus_privileges.hpp index fca4a137d3..07a1216cdc 100644 --- a/include/dbus_privileges.hpp +++ b/include/dbus_privileges.hpp @@ -6,9 +6,11 @@ #include "http_response.hpp" #include "logging.hpp" #include "routing/baserule.hpp" +#include "user_role_map.hpp" #include "utils/dbus_utils.hpp" #include <boost/url/format.hpp> +#include <sdbusplus/bus/match.hpp> #include <sdbusplus/unpack_properties.hpp> #include <memory> @@ -16,82 +18,6 @@ namespace crow { -// Populate session with user information. -inline bool - populateUserInfo(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - const dbus::utility::DBusPropertiesMap& userInfoMap) -{ - const std::string* userRolePtr = nullptr; - const bool* remoteUser = nullptr; - const bool* passwordExpired = nullptr; - const std::vector<std::string>* userGroups = nullptr; - - const bool success = sdbusplus::unpackPropertiesNoThrow( - redfish::dbus_utils::UnpackErrorPrinter(), userInfoMap, "UserPrivilege", - userRolePtr, "RemoteUser", remoteUser, "UserPasswordExpired", - passwordExpired, "UserGroups", userGroups); - - if (!success) - { - BMCWEB_LOG_ERROR("Failed to unpack user properties."); - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return false; - } - - if (req.session == nullptr) - { - return false; - } - - if (userRolePtr != nullptr) - { - req.session->userRole = *userRolePtr; - BMCWEB_LOG_DEBUG("userName = {} userRole = {}", req.session->username, - *userRolePtr); - } - - if (remoteUser == nullptr) - { - BMCWEB_LOG_ERROR("RemoteUser property missing or wrong type"); - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return false; - } - bool expired = false; - if (passwordExpired == nullptr) - { - if (!*remoteUser) - { - BMCWEB_LOG_ERROR("UserPasswordExpired property is expected for" - " local user but is missing or wrong type"); - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return false; - } - } - else - { - expired = *passwordExpired; - } - - // Set isConfigureSelfOnly based on D-Bus results. This - // ignores the results from both pamAuthenticateUser and the - // value from any previous use of this session. - req.session->isConfigureSelfOnly = expired; - - if (userGroups != nullptr) - { - // Populate session with user groups. - for (const auto& userGroup : *userGroups) - { - req.session->userGroups.emplace_back(userGroup); - } - } - - return true; -} inline bool isUserPrivileged(Request& req, @@ -128,62 +54,42 @@ inline bool return false; } - req.userRole = req.session->userRole; return true; } template <typename CallbackFn> -void afterGetUserInfo(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - BaseRule& rule, CallbackFn&& callback, - const boost::system::error_code& ec, - const dbus::utility::DBusPropertiesMap& userInfoMap) +void validatePrivilege(Request& req, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + BaseRule& rule, CallbackFn&& callback) { - if (ec) + if (req.session == nullptr) { - BMCWEB_LOG_ERROR("GetUserInfo failed..."); - asyncResp->res.result( - boost::beast::http::status::internal_server_error); return; } - - if (!populateUserInfo(req, asyncResp, userInfoMap)) + std::string username = req.session->username; + UserFields props = + UserRoleMap::getInstance().getUserRole(req.session->username); + if (props.userRole) { - BMCWEB_LOG_ERROR("Failed to populate user information"); - asyncResp->res.result( - boost::beast::http::status::internal_server_error); - return; + req.session->userRole = props.userRole.value_or(""); + } + if (props.passwordExpired) + { + req.session->isConfigureSelfOnly = *props.passwordExpired; + } + if (props.userGroups) + { + req.session->userGroups = std::move(*props.userGroups); } if (!isUserPrivileged(req, asyncResp, rule)) { // User is not privileged - BMCWEB_LOG_ERROR("Insufficient Privilege"); + BMCWEB_LOG_WARNING("Insufficient Privilege"); asyncResp->res.result(boost::beast::http::status::forbidden); return; } callback(req); } -template <typename CallbackFn> -void validatePrivilege(Request& req, - const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, - BaseRule& rule, CallbackFn&& callback) -{ - if (req.session == nullptr) - { - return; - } - std::string username = req.session->username; - crow::connections::systemBus->async_method_call( - [&req, asyncResp, &rule, callback(std::forward<CallbackFn>(callback))]( - const boost::system::error_code& ec, - const dbus::utility::DBusPropertiesMap& userInfoMap) mutable { - afterGetUserInfo(req, asyncResp, rule, - std::forward<CallbackFn>(callback), ec, userInfoMap); - }, - "xyz.openbmc_project.User.Manager", "/xyz/openbmc_project/user", - "xyz.openbmc_project.User.Manager", "GetUserInfo", username); -} - } // namespace crow diff --git a/include/sessions.hpp b/include/sessions.hpp index cb7f78e78a..90a1de93de 100644 --- a/include/sessions.hpp +++ b/include/sessions.hpp @@ -2,10 +2,12 @@ #include "logging.hpp" #include "ossl_random.hpp" +#include "user_role_map.hpp" #include "utility.hpp" #include "utils/ip_utils.hpp" #include <nlohmann/json.hpp> +#include <sdbusplus/message.hpp> #include <algorithm> #include <csignal> @@ -256,11 +258,15 @@ class SessionStore } } + std::string userRole = crow::UserRoleMap::getInstance() + .getUserRole(username) + .userRole.value_or(""); + auto session = std::make_shared<UserSession>(UserSession{ uniqueId, sessionToken, std::string(username), csrfToken, clientId, redfish::ip_util::toString(clientIp), std::chrono::steady_clock::now(), persistence, false, - isConfigureSelfOnly}); + isConfigureSelfOnly, userRole}); auto it = authTokens.emplace(sessionToken, session); // Only need to write to disk if session isn't about to be destroyed. needWrite = persistence == PersistenceType::TIMEOUT; diff --git a/include/user_role_map.hpp b/include/user_role_map.hpp new file mode 100644 index 0000000000..ce2f97ed0c --- /dev/null +++ b/include/user_role_map.hpp @@ -0,0 +1,278 @@ +#pragma once + +#include "dbus_utility.hpp" +#include "logging.hpp" +#include "utils/dbus_utils.hpp" + +#include <boost/container/flat_map.hpp> +#include <boost/url/format.hpp> +#include <sdbusplus/bus/match.hpp> +#include <sdbusplus/unpack_properties.hpp> + +#include <functional> +#include <memory> +#include <optional> +#include <string> +#include <variant> +#include <vector> + +namespace crow +{ +struct UserFields +{ + std::optional<std::string> userRole; + std::optional<bool> remote; + std::optional<bool> passwordExpired; + std::optional<std::vector<std::string>> userGroups; +}; + +struct UserRoleMap +{ + public: + static UserRoleMap& getInstance() + { + static UserRoleMap userRoleMap; + return userRoleMap; + } + + UserFields getUserRole(std::string_view name) + { + auto it = roleMap.find(name); + if (it == roleMap.end()) + { + BMCWEB_LOG_ERROR("User name {} is not found in the UserRoleMap.", + name); + return {}; + } + return it->second; + } + UserRoleMap(const UserRoleMap&) = delete; + UserRoleMap& operator=(const UserRoleMap&) = delete; + UserRoleMap(UserRoleMap&&) = delete; + UserRoleMap& operator=(UserRoleMap&&) = delete; + ~UserRoleMap() = default; + + private: + static UserFields extractUserRole( + const dbus::utility::DBusInteracesMap& interfacesProperties) + { + UserFields fields; + for (const auto& interface : interfacesProperties) + { + for (const auto& property : interface.second) + { + if (property.first == "UserPrivilege") + { + const std::string* role = + std::get_if<std::string>(&property.second); + if (role != nullptr) + { + fields.userRole = *role; + } + } + else if (property.first == "UserGroups") + { + const std::vector<std::string>* groups = + std::get_if<std::vector<std::string>>(&property.second); + if (groups != nullptr) + { + fields.userGroups = *groups; + } + } + else if (property.first == "UserPasswordExpired") + { + const bool* expired = std::get_if<bool>(&property.second); + if (expired != nullptr) + { + fields.passwordExpired = *expired; + } + } + else if (property.first == "RemoteUser") + { + const bool* remote = std::get_if<bool>(&property.second); + if (remote != nullptr) + { + fields.remote = *remote; + } + } + } + } + return fields; + } + + void userAdded(sdbusplus::message::message& m) + { + BMCWEB_LOG_DEBUG("User Added"); + sdbusplus::message::object_path objPath; + dbus::utility::DBusInteracesMap interfacesProperties; + + try + { + m.read(objPath, interfacesProperties); + } + catch (const sdbusplus::exception::SdBusError& e) + { + BMCWEB_LOG_ERROR( + "Failed to parse user add signal.ERROR={}REPLY_SIG={}", + e.what(), m.get_signature()); + return; + } + BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); + + std::string name = objPath.filename(); + if (name.empty()) + { + return; + } + UserFields role = extractUserRole(interfacesProperties); + + // Insert the newly added user name and the role + auto res = roleMap.emplace(name, role); + if (!res.second) + { + BMCWEB_LOG_ERROR( + "Insertion of the user=\"{}\" in the roleMap failed.", name); + return; + } + } + + void userRemoved(sdbusplus::message::message& m) + { + BMCWEB_LOG_DEBUG("User Removed"); + sdbusplus::message::object_path objPath; + + try + { + m.read(objPath); + } + catch (const sdbusplus::exception::SdBusError& e) + { + BMCWEB_LOG_ERROR("Failed to parse user delete signal."); + BMCWEB_LOG_ERROR("ERROR={}REPLY_SIG={}", e.what(), + m.get_signature()); + return; + } + + BMCWEB_LOG_DEBUG("obj path = {}", objPath.str); + + std::string name = objPath.filename(); + if (name.empty()) + { + return; + } + + roleMap.erase(name); + } + + void userPropertiesChanged(sdbusplus::message::message& m) + { + BMCWEB_LOG_DEBUG("Properties Changed"); + std::string interface; + dbus::utility::DBusPropertiesMap changedProperties; + try + { + m.read(interface, changedProperties); + } + catch (const sdbusplus::exception::SdBusError& e) + { + BMCWEB_LOG_ERROR("Failed to parse user properties changed signal."); + BMCWEB_LOG_ERROR("ERROR={}REPLY_SIG={}", e.what(), + m.get_signature()); + return; + } + dbus::utility::DBusInteracesMap map; + map.emplace_back("xyz.openbmc_project.User.Attributes", + changedProperties); + const sdbusplus::message::object_path path(m.get_path()); + + BMCWEB_LOG_DEBUG("Object Path = \"{}\"", path.str); + + std::string user = path.filename(); + if (user.empty()) + { + return; + } + + BMCWEB_LOG_DEBUG("User Name = \"{}\"", user); + + UserFields role = extractUserRole(map); + + auto userProps = roleMap.find(user); + if (userProps == roleMap.end()) + { + BMCWEB_LOG_CRITICAL("User {} not found", user); + return; + } + if (role.userRole) + { + userProps->second.userRole = role.userRole; + } + if (role.remote) + { + userProps->second.remote = role.remote; + } + if (role.userGroups) + { + userProps->second.userGroups = role.userGroups; + } + if (role.passwordExpired) + { + userProps->second.passwordExpired = role.passwordExpired; + } + } + + void onGetManagedObjects( + const boost::system::error_code& ec, + const dbus::utility::ManagedObjectType& managedObjects) + { + if (ec) + { + BMCWEB_LOG_DEBUG("User manager call failed, ignoring"); + return; + } + + for (const auto& managedObj : managedObjects) + { + std::string name = + sdbusplus::message::object_path(managedObj.first).filename(); + if (name.empty()) + { + continue; + } + UserFields role = extractUserRole(managedObj.second); + roleMap.emplace(name, role); + } + } + + static constexpr const char* userObjPath = "/xyz/openbmc_project/user"; + + UserRoleMap() : + userAddedSignal( + *crow::connections::systemBus, + sdbusplus::bus::match::rules::interfacesAdded(userObjPath), + std::bind_front(&UserRoleMap::userAdded, this)), + userRemovedSignal( + *crow::connections::systemBus, + sdbusplus::bus::match::rules::interfacesRemoved(userObjPath), + std::bind_front(&UserRoleMap::userRemoved, this)), + userPropertiesChangedSignal( + *crow::connections::systemBus, + sdbusplus::bus::match::rules::propertiesChangedNamespace( + userObjPath, "xyz.openbmc_project.User.Attributes"), + std::bind_front(&UserRoleMap::userPropertiesChanged, this)) + { + dbus::utility::getManagedObjects( + "xyz.openbmc_project.User.Manager", {userObjPath}, + std::bind_front(&UserRoleMap::onGetManagedObjects, this)); + } + + // Map of username -> role + boost::container::flat_map<std::string, UserFields, std::less<>> roleMap; + + // These MUST be last, otherwise destruction can cause race conditions. + sdbusplus::bus::match_t userAddedSignal; + sdbusplus::bus::match_t userRemovedSignal; + sdbusplus::bus::match_t userPropertiesChangedSignal; +}; + +} // namespace crow diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp index 67e2aaef04..34fe18f07d 100644 --- a/src/webserver_main.cpp +++ b/src/webserver_main.cpp @@ -18,6 +18,7 @@ #include "security_headers.hpp" #include "ssl_key_handler.hpp" #include "user_monitor.hpp" +#include "user_role_map.hpp" #include "vm_websocket.hpp" #include "webassets.hpp" @@ -143,6 +144,9 @@ static int run() crow::hostname_monitor::registerHostnameSignal(); #endif + // Init the user role map + crow::UserRoleMap::getInstance(); + bmcweb::registerUserRemovedSignal(); app.run(); |