diff --git a/src/auth/models.cpp b/src/auth/models.cpp index 8485685dc..90740a752 100644 --- a/src/auth/models.cpp +++ b/src/auth/models.cpp @@ -101,6 +101,35 @@ std::string PermissionLevelToString(PermissionLevel level) { } } +FineGrainedPermission PermissionToFineGrainedPermission(const uint64_t permission) { + if (permission & FineGrainedPermission::CREATE_DELETE) { + return FineGrainedPermission::CREATE_DELETE; + } + + if (permission & FineGrainedPermission::UPDATE) { + return FineGrainedPermission::UPDATE; + } + + if (permission & FineGrainedPermission::READ) { + return FineGrainedPermission::READ; + } + + return FineGrainedPermission::NO_PERMISSION; +} + +std::string FineGrainedPermissionToString(const FineGrainedPermission level) { + switch (level) { + case FineGrainedPermission::CREATE_DELETE: + return "CREATE_DELETE"; + case FineGrainedPermission::UPDATE: + return "UPDATE"; + case FineGrainedPermission::READ: + return "READ"; + case FineGrainedPermission::NO_PERMISSION: + return "NO_PERMISSION"; + } +} + FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) { std::unordered_map<std::string, uint64_t> permissions{first.GetPermissions()}; @@ -370,6 +399,14 @@ Permissions &Role::permissions() { return permissions_; } const FineGrainedAccessHandler &Role::fine_grained_access_handler() const { return fine_grained_access_handler_; } FineGrainedAccessHandler &Role::fine_grained_access_handler() { return fine_grained_access_handler_; } +const FineGrainedAccessPermissions &Role::GetFineGrainedAccessLabelPermissions() const { + return fine_grained_access_handler_.label_permissions(); +} + +const FineGrainedAccessPermissions &Role::GetFineGrainedAccessEdgeTypePermissions() const { + return fine_grained_access_handler_.edge_type_permissions(); +} + nlohmann::json Role::Serialize() const { nlohmann::json data = nlohmann::json::object(); data["rolename"] = rolename_; diff --git a/src/auth/models.hpp b/src/auth/models.hpp index 3a9ec3260..6de0b40fb 100644 --- a/src/auth/models.hpp +++ b/src/auth/models.hpp @@ -46,6 +46,7 @@ enum class Permission : uint64_t { // clang-format off enum class FineGrainedPermission : uint64_t { + NO_PERMISSION = 0, READ = 1, UPDATE = 1U << 1U, CREATE_DELETE = 1U << 2U @@ -79,6 +80,12 @@ enum class PermissionLevel : uint8_t { GRANT, NEUTRAL, DENY }; // Function that converts a permission level to its string representation. std::string PermissionLevelToString(PermissionLevel level); +// Function that converts a label permission level to its string representation. +std::string FineGrainedPermissionToString(FineGrainedPermission level); + +// Constructs a label permission from a permission +FineGrainedPermission PermissionToFineGrainedPermission(uint64_t permission); + class Permissions final { public: explicit Permissions(uint64_t grants = 0, uint64_t denies = 0); @@ -205,6 +212,8 @@ class Role final { Permissions &permissions(); const FineGrainedAccessHandler &fine_grained_access_handler() const; FineGrainedAccessHandler &fine_grained_access_handler(); + const FineGrainedAccessPermissions &GetFineGrainedAccessLabelPermissions() const; + const FineGrainedAccessPermissions &GetFineGrainedAccessEdgeTypePermissions() const; nlohmann::json Serialize() const; diff --git a/src/glue/CMakeLists.txt b/src/glue/CMakeLists.txt index 835b7c08a..fa8a87997 100644 --- a/src/glue/CMakeLists.txt +++ b/src/glue/CMakeLists.txt @@ -1,4 +1,4 @@ -set(mg_glue_sources auth.cpp auth_checker.cpp communication.cpp) +set(mg_glue_sources auth.cpp auth_checker.cpp auth_handler.cpp communication.cpp) add_library(mg-glue STATIC ${mg_glue_sources}) target_link_libraries(mg-glue mg-query mg-auth) diff --git a/src/glue/auth_handler.cpp b/src/glue/auth_handler.cpp new file mode 100644 index 000000000..aaed51c66 --- /dev/null +++ b/src/glue/auth_handler.cpp @@ -0,0 +1,587 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include "glue/auth_handler.hpp" + +#include <sstream> + +#include <fmt/format.h> + +#include "auth/models.hpp" +#include "glue/auth.hpp" + +namespace { + +struct PermissionForPrivilegeResult { + std::string permission; + memgraph::auth::PermissionLevel permission_level; + std::string description; +}; + +struct FineGrainedPermissionForPrivilegeResult { + std::string permission; + memgraph::auth::FineGrainedPermission permission_level; + std::string description; +}; + +PermissionForPrivilegeResult GetPermissionForPrivilegeForUserOrRole( + const memgraph::auth::Permissions &permissions, const memgraph::query::AuthQuery::Privilege &privilege, + const std::string &user_or_role) { + PermissionForPrivilegeResult container; + + const auto permission = memgraph::glue::PrivilegeToPermission(privilege); + container.permission = memgraph::auth::PermissionToString(permission); + container.permission_level = permissions.Has(permission); + + switch (container.permission_level) { + case memgraph::auth::PermissionLevel::GRANT: + container.description = "GRANTED TO " + user_or_role; + break; + case memgraph::auth::PermissionLevel::DENY: + container.description = "DENIED TO " + user_or_role; + break; + case memgraph::auth::PermissionLevel::NEUTRAL: + break; + } + + return container; +} + +std::vector<std::vector<memgraph::query::TypedValue>> ConstructPrivilegesResult( + const std::vector<PermissionForPrivilegeResult> &privileges) { + std::vector<std::vector<memgraph::query::TypedValue>> grants; + + grants.reserve(privileges.size()); + for (const auto &permission : privileges) { + grants.push_back({memgraph::query::TypedValue(permission.permission), + memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(permission.permission_level)), + memgraph::query::TypedValue(permission.description)}); + } + + return grants; +} + +std::vector<std::vector<memgraph::query::TypedValue>> ShowUserPrivileges( + const std::optional<memgraph::auth::User> &user) { + std::vector<PermissionForPrivilegeResult> privilege_results; + + const auto &permissions = user->GetPermissions(); + const auto &user_level_permissions = user->permissions(); + + for (const auto &privilege : memgraph::query::kPrivilegesAll) { + auto user_permission_result = GetPermissionForPrivilegeForUserOrRole(permissions, privilege, "USER"); + auto user_only_permissions_result = + GetPermissionForPrivilegeForUserOrRole(user_level_permissions, privilege, "USER"); + + if (user_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) { + std::vector<std::string> full_description; + if (user_only_permissions_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) { + full_description.emplace_back(user_only_permissions_result.description); + } + + if (const auto *role = user->role(); role != nullptr) { + auto role_permission_result = GetPermissionForPrivilegeForUserOrRole(role->permissions(), privilege, "ROLE"); + if (role_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) { + full_description.emplace_back(role_permission_result.description); + } + } + + privilege_results.push_back(PermissionForPrivilegeResult{user_permission_result.permission, + user_permission_result.permission_level, + memgraph::utils::Join(full_description, ", ")}); + } + } + + return ConstructPrivilegesResult(privilege_results); +} + +std::vector<std::vector<memgraph::query::TypedValue>> ShowRolePrivileges( + const std::optional<memgraph::auth::Role> &role) { + std::vector<PermissionForPrivilegeResult> privilege_results; + const auto &permissions = role->permissions(); + for (const auto &privilege : memgraph::query::kPrivilegesAll) { + auto role_permission_result = GetPermissionForPrivilegeForUserOrRole(permissions, privilege, "ROLE"); + if (role_permission_result.permission_level != memgraph::auth::PermissionLevel::NEUTRAL) { + privilege_results.push_back(role_permission_result); + } + } + + return ConstructPrivilegesResult(privilege_results); +} + +std::vector<FineGrainedPermissionForPrivilegeResult> GetFineGrainedPermissionForPrivilegeForUserOrRole( + const memgraph::auth::FineGrainedAccessPermissions &permissions, const std::string &permission_type, + const std::string &user_or_role) { + std::vector<FineGrainedPermissionForPrivilegeResult> fine_grained_permissions; + const auto global_permission = permissions.GetGlobalPermission(); + if (global_permission.has_value()) { + const auto &permission_level = memgraph::auth::PermissionToFineGrainedPermission(global_permission.value()); + + std::stringstream permission_representation; + permission_representation << "ALL " << permission_type << "S"; + const auto &permission_level_representation = + permission_level == memgraph::auth::FineGrainedPermission::NO_PERMISSION ? "DENIED" : "GRANTED"; + + const auto permission_description = + fmt::format("GLOBAL {0} PERMISSION {1} TO {2}", permission_type, permission_level_representation, user_or_role); + + fine_grained_permissions.push_back(FineGrainedPermissionForPrivilegeResult{ + permission_representation.str(), permission_level, permission_description}); + } + + for (const auto &[label, permission] : permissions.GetPermissions()) { + auto permission_level = memgraph::auth::PermissionToFineGrainedPermission(permission); + + std::stringstream permission_representation; + permission_representation << permission_type << " :" << label; + + const auto &permission_level_representation = + permission_level == memgraph::auth::FineGrainedPermission::NO_PERMISSION ? "DENIED" : "GRANTED"; + + const auto permission_description = + fmt::format("{0} PERMISSION {1} TO {2}", permission_type, permission_level_representation, user_or_role); + + fine_grained_permissions.push_back(FineGrainedPermissionForPrivilegeResult{ + permission_representation.str(), permission_level, permission_description}); + } + + return fine_grained_permissions; +} + +std::vector<std::vector<memgraph::query::TypedValue>> ConstructFineGrainedPrivilegesResult( + const std::vector<FineGrainedPermissionForPrivilegeResult> &privileges) { + std::vector<std::vector<memgraph::query::TypedValue>> grants; + + grants.reserve(privileges.size()); + for (const auto &permission : privileges) { + grants.push_back( + {memgraph::query::TypedValue(permission.permission), + memgraph::query::TypedValue(memgraph::auth::FineGrainedPermissionToString(permission.permission_level)), + memgraph::query::TypedValue(permission.description)}); + } + + return grants; +} + +std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedUserPrivileges( + const std::optional<memgraph::auth::User> &user) { + const auto &label_permissions = user->GetFineGrainedAccessLabelPermissions(); + const auto &edge_type_permissions = user->GetFineGrainedAccessEdgeTypePermissions(); + + auto all_fine_grained_permissions = + GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER"); + auto edge_type_fine_grained_permissions = + GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER"); + + all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(), + edge_type_fine_grained_permissions.end()); + + return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions); +} + +std::vector<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivileges( + const std::optional<memgraph::auth::Role> &role) { + const auto &label_permissions = role->GetFineGrainedAccessLabelPermissions(); + const auto &edge_type_permissions = role->GetFineGrainedAccessEdgeTypePermissions(); + + auto all_fine_grained_permissions = + GetFineGrainedPermissionForPrivilegeForUserOrRole(label_permissions, "LABEL", "USER"); + auto edge_type_fine_grained_permissions = + GetFineGrainedPermissionForPrivilegeForUserOrRole(edge_type_permissions, "EDGE_TYPE", "USER"); + + all_fine_grained_permissions.insert(all_fine_grained_permissions.end(), edge_type_fine_grained_permissions.begin(), + edge_type_fine_grained_permissions.end()); + + return ConstructFineGrainedPrivilegesResult(all_fine_grained_permissions); +} +} // namespace + +namespace memgraph::glue { + +AuthQueryHandler::AuthQueryHandler( + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, + std::string name_regex_string) + : auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {} + +bool AuthQueryHandler::CreateUser(const std::string &username, const std::optional<std::string> &password) { + if (name_regex_string_ != kDefaultUserRoleRegex) { + if (const auto license_check_result = + memgraph::utils::license::global_license_checker.IsValidLicense(memgraph::utils::global_settings); + license_check_result.HasError()) { + throw memgraph::auth::AuthException( + "Custom user/role regex is a Memgraph Enterprise feature. Please set the config " + "(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}", + kDefaultUserRoleRegex, + memgraph::utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex")); + } + } + if (!std::regex_match(username, name_regex_)) { + throw query::QueryRuntimeException("Invalid user name."); + } + try { + const auto [first_user, user_added] = std::invoke([&, this] { + auto locked_auth = auth_->Lock(); + const auto first_user = !locked_auth->HasUsers(); + const auto user_added = locked_auth->AddUser(username, password).has_value(); + return std::make_pair(first_user, user_added); + }); + + if (first_user) { + spdlog::info("{} is first created user. Granting all privileges.", username); + GrantPrivilege( + username, memgraph::query::kPrivilegesAll, + {{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}}, + {{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}}); + } + + return user_added; + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +bool AuthQueryHandler::DropUser(const std::string &username) { + if (!std::regex_match(username, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user name."); + } + try { + auto locked_auth = auth_->Lock(); + auto user = locked_auth->GetUser(username); + if (!user) return false; + return locked_auth->RemoveUser(username); + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +void AuthQueryHandler::SetPassword(const std::string &username, const std::optional<std::string> &password) { + if (!std::regex_match(username, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user name."); + } + try { + auto locked_auth = auth_->Lock(); + auto user = locked_auth->GetUser(username); + if (!user) { + throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username); + } + user->UpdatePassword(password); + locked_auth->SaveUser(*user); + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +bool AuthQueryHandler::CreateRole(const std::string &rolename) { + if (!std::regex_match(rolename, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid role name."); + } + try { + auto locked_auth = auth_->Lock(); + return locked_auth->AddRole(rolename).has_value(); + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +bool AuthQueryHandler::DropRole(const std::string &rolename) { + if (!std::regex_match(rolename, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid role name."); + } + try { + auto locked_auth = auth_->Lock(); + auto role = locked_auth->GetRole(rolename); + + if (!role) { + return false; + }; + + return locked_auth->RemoveRole(rolename); + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernames() { + try { + auto locked_auth = auth_->ReadLock(); + std::vector<memgraph::query::TypedValue> usernames; + const auto &users = locked_auth->AllUsers(); + usernames.reserve(users.size()); + for (const auto &user : users) { + usernames.emplace_back(user.username()); + } + return usernames; + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetRolenames() { + try { + auto locked_auth = auth_->ReadLock(); + std::vector<memgraph::query::TypedValue> rolenames; + const auto &roles = locked_auth->AllRoles(); + rolenames.reserve(roles.size()); + for (const auto &role : roles) { + rolenames.emplace_back(role.rolename()); + } + return rolenames; + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +std::optional<std::string> AuthQueryHandler::GetRolenameForUser(const std::string &username) { + if (!std::regex_match(username, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user name."); + } + try { + auto locked_auth = auth_->ReadLock(); + auto user = locked_auth->GetUser(username); + if (!user) { + throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username); + } + + if (const auto *role = user->role(); role != nullptr) { + return role->rolename(); + } + return std::nullopt; + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +std::vector<memgraph::query::TypedValue> AuthQueryHandler::GetUsernamesForRole(const std::string &rolename) { + if (!std::regex_match(rolename, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid role name."); + } + try { + auto locked_auth = auth_->ReadLock(); + auto role = locked_auth->GetRole(rolename); + if (!role) { + throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist.", rolename); + } + std::vector<memgraph::query::TypedValue> usernames; + const auto &users = locked_auth->AllUsersForRole(rolename); + usernames.reserve(users.size()); + for (const auto &user : users) { + usernames.emplace_back(user.username()); + } + return usernames; + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +void AuthQueryHandler::SetRole(const std::string &username, const std::string &rolename) { + if (!std::regex_match(username, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user name."); + } + if (!std::regex_match(rolename, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid role name."); + } + try { + auto locked_auth = auth_->Lock(); + auto user = locked_auth->GetUser(username); + if (!user) { + throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username); + } + auto role = locked_auth->GetRole(rolename); + if (!role) { + throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist .", rolename); + } + if (const auto *current_role = user->role(); current_role != nullptr) { + throw memgraph::query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username, + current_role->rolename()); + } + user->SetRole(*role); + locked_auth->SaveUser(*user); + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +void AuthQueryHandler::ClearRole(const std::string &username) { + if (!std::regex_match(username, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user name."); + } + try { + auto locked_auth = auth_->Lock(); + auto user = locked_auth->GetUser(username); + if (!user) { + throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username); + } + user->ClearRole(); + locked_auth->SaveUser(*user); + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +std::vector<std::vector<memgraph::query::TypedValue>> AuthQueryHandler::GetPrivileges(const std::string &user_or_role) { + if (!std::regex_match(user_or_role, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user or role name."); + } + try { + auto locked_auth = auth_->ReadLock(); + std::vector<std::vector<memgraph::query::TypedValue>> grants; + std::vector<std::vector<memgraph::query::TypedValue>> fine_grained_grants; + auto user = locked_auth->GetUser(user_or_role); + auto role = locked_auth->GetRole(user_or_role); + if (!user && !role) { + throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role); + } + + if (user) { + grants = ShowUserPrivileges(user); + fine_grained_grants = ShowFineGrainedUserPrivileges(user); + } else { + grants = ShowRolePrivileges(role); + fine_grained_grants = ShowFineGrainedRolePrivileges(role); + } + + grants.insert(grants.end(), fine_grained_grants.begin(), fine_grained_grants.end()); + + return grants; + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +void AuthQueryHandler::GrantPrivilege( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges) { + EditPermissions( + user_or_role, privileges, label_privileges, edge_type_privileges, + [](auto &permissions, const auto &permission) { + // TODO (mferencevic): should we first check that the + // privilege is granted/denied/revoked before + // unconditionally granting/denying/revoking it? + permissions.Grant(permission); + }, + [](auto &fine_grained_permissions, const auto &privilege_collection) { + for (const auto &[privilege, entities] : privilege_collection) { + const auto &permission = memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(privilege); + for (const auto &entity : entities) { + fine_grained_permissions.Grant(entity, permission); + } + } + }); +} + +void AuthQueryHandler::DenyPrivilege( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges) { + EditPermissions( + user_or_role, privileges, label_privileges, edge_type_privileges, + [](auto &permissions, const auto &permission) { + // TODO (mferencevic): should we first check that the + // privilege is granted/denied/revoked before + // unconditionally granting/denying/revoking it? + permissions.Deny(permission); + }, + [](auto &fine_grained_permissions, const auto &privilege_collection) { + for (const auto &[privilege, entities] : privilege_collection) { + const auto &permission = memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(privilege); + for (const auto &entity : entities) { + fine_grained_permissions.Deny(entity, permission); + } + } + }); +} + +void AuthQueryHandler::RevokePrivilege( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges) { + EditPermissions( + user_or_role, privileges, label_privileges, edge_type_privileges, + [](auto &permissions, const auto &permission) { + // TODO (mferencevic): should we first check that the + // privilege is granted/denied/revoked before + // unconditionally granting/denying/revoking it? + permissions.Revoke(permission); + }, + [](auto &fine_grained_permissions, const auto &privilege_collection) { + for ([[maybe_unused]] const auto &[privilege, entities] : privilege_collection) { + for (const auto &entity : entities) { + fine_grained_permissions.Revoke(entity); + } + } + }); +} + +template <class TEditPermissionsFun, class TEditFineGrainedPermissionsFun> +void AuthQueryHandler::EditPermissions( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges, + const TEditPermissionsFun &edit_permissions_fun, + const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun) { + if (!std::regex_match(user_or_role, name_regex_)) { + throw memgraph::query::QueryRuntimeException("Invalid user or role name."); + } + try { + std::vector<memgraph::auth::Permission> permissions; + permissions.reserve(privileges.size()); + for (const auto &privilege : privileges) { + permissions.push_back(memgraph::glue::PrivilegeToPermission(privilege)); + } + auto locked_auth = auth_->Lock(); + auto user = locked_auth->GetUser(user_or_role); + auto role = locked_auth->GetRole(user_or_role); + if (!user && !role) { + throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role); + } + if (user) { + for (const auto &permission : permissions) { + edit_permissions_fun(user->permissions(), permission); + } + for (const auto &label_privilege_collection : label_privileges) { + edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), + label_privilege_collection); + } + for (const auto &edge_type_privilege_collection : edge_type_privileges) { + edit_fine_grained_permissions_fun(user->fine_grained_access_handler().edge_type_permissions(), + edge_type_privilege_collection); + } + + locked_auth->SaveUser(*user); + } else { + for (const auto &permission : permissions) { + edit_permissions_fun(role->permissions(), permission); + } + for (const auto &label_privilege : label_privileges) { + edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), label_privilege); + } + for (const auto &edge_type_privilege : edge_type_privileges) { + edit_fine_grained_permissions_fun(role->fine_grained_access_handler().edge_type_permissions(), + edge_type_privilege); + } + + locked_auth->SaveRole(*role); + } + } catch (const memgraph::auth::AuthException &e) { + throw memgraph::query::QueryRuntimeException(e.what()); + } +} + +} // namespace memgraph::glue diff --git a/src/glue/auth_handler.hpp b/src/glue/auth_handler.hpp new file mode 100644 index 000000000..976ffb6e9 --- /dev/null +++ b/src/glue/auth_handler.hpp @@ -0,0 +1,91 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include <regex> + +#include "auth/auth.hpp" +#include "glue/auth.hpp" +#include "query/interpreter.hpp" +#include "utils/license.hpp" +#include "utils/string.hpp" + +namespace memgraph::glue { + +inline constexpr std::string_view kDefaultUserRoleRegex = "[a-zA-Z0-9_.+-@]+"; + +class AuthQueryHandler final : public memgraph::query::AuthQueryHandler { + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_; + std::string name_regex_string_; + std::regex name_regex_; + + public: + AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, + std::string name_regex_string); + + bool CreateUser(const std::string &username, const std::optional<std::string> &password) override; + + bool DropUser(const std::string &username) override; + + void SetPassword(const std::string &username, const std::optional<std::string> &password) override; + + bool CreateRole(const std::string &rolename) override; + + bool DropRole(const std::string &rolename) override; + + std::vector<memgraph::query::TypedValue> GetUsernames() override; + + std::vector<memgraph::query::TypedValue> GetRolenames() override; + + std::optional<std::string> GetRolenameForUser(const std::string &username) override; + + std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override; + + void SetRole(const std::string &username, const std::string &rolename) override; + + void ClearRole(const std::string &username) override; + + std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override; + + void GrantPrivilege( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges) override; + + void DenyPrivilege( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges) override; + + void RevokePrivilege( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges) override; + + private: + template <class TEditPermissionsFun, class TEditFineGrainedPermissionsFun> + void EditPermissions( + const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &label_privileges, + const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> + &edge_type_privileges, + const TEditPermissionsFun &edit_permissions_fun, + const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun); +}; +} // namespace memgraph::glue diff --git a/src/memgraph.cpp b/src/memgraph.cpp index b5ceff715..bf635e531 100644 --- a/src/memgraph.cpp +++ b/src/memgraph.cpp @@ -38,6 +38,7 @@ #include "communication/websocket/auth.hpp" #include "communication/websocket/server.hpp" #include "glue/auth_checker.hpp" +#include "glue/auth_handler.hpp" #include "helpers.hpp" #include "py/py.hpp" #include "query/auth_checker.hpp" @@ -469,424 +470,10 @@ struct SessionData { #endif }; -inline constexpr std::string_view default_user_role_regex = "[a-zA-Z0-9_.+-@]+"; // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -DEFINE_string(auth_user_or_role_name_regex, default_user_role_regex.data(), +DEFINE_string(auth_user_or_role_name_regex, memgraph::glue::kDefaultUserRoleRegex.data(), "Set to the regular expression that each user or role name must fulfill."); -class AuthQueryHandler final : public memgraph::query::AuthQueryHandler { - memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_; - std::string name_regex_string_; - std::regex name_regex_; - - public: - AuthQueryHandler(memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth, - std::string name_regex_string) - : auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {} - - bool CreateUser(const std::string &username, const std::optional<std::string> &password) override { - if (name_regex_string_ != default_user_role_regex) { - if (const auto license_check_result = - memgraph::utils::license::global_license_checker.IsValidLicense(memgraph::utils::global_settings); - license_check_result.HasError()) { - throw memgraph::auth::AuthException( - "Custom user/role regex is a Memgraph Enterprise feature. Please set the config " - "(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}", - default_user_role_regex, - memgraph::utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex")); - } - } - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - try { - const auto [first_user, user_added] = std::invoke([&, this] { - auto locked_auth = auth_->Lock(); - const auto first_user = !locked_auth->HasUsers(); - const auto user_added = locked_auth->AddUser(username, password).has_value(); - return std::make_pair(first_user, user_added); - }); - - if (first_user) { - spdlog::info("{} is first created user. Granting all privileges.", username); - GrantPrivilege( - username, memgraph::query::kPrivilegesAll, - {{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}}, - {{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}}); - } - - return user_added; - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - bool DropUser(const std::string &username) override { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - try { - auto locked_auth = auth_->Lock(); - auto user = locked_auth->GetUser(username); - if (!user) return false; - return locked_auth->RemoveUser(username); - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - void SetPassword(const std::string &username, const std::optional<std::string> &password) override { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - try { - auto locked_auth = auth_->Lock(); - auto user = locked_auth->GetUser(username); - if (!user) { - throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist.", username); - } - user->UpdatePassword(password); - locked_auth->SaveUser(*user); - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - bool CreateRole(const std::string &rolename) override { - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } - try { - auto locked_auth = auth_->Lock(); - return locked_auth->AddRole(rolename).has_value(); - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - bool DropRole(const std::string &rolename) override { - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } - try { - auto locked_auth = auth_->Lock(); - auto role = locked_auth->GetRole(rolename); - if (!role) return false; - return locked_auth->RemoveRole(rolename); - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - std::vector<memgraph::query::TypedValue> GetUsernames() override { - try { - auto locked_auth = auth_->ReadLock(); - std::vector<memgraph::query::TypedValue> usernames; - const auto &users = locked_auth->AllUsers(); - usernames.reserve(users.size()); - for (const auto &user : users) { - usernames.emplace_back(user.username()); - } - return usernames; - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - std::vector<memgraph::query::TypedValue> GetRolenames() override { - try { - auto locked_auth = auth_->ReadLock(); - std::vector<memgraph::query::TypedValue> rolenames; - const auto &roles = locked_auth->AllRoles(); - rolenames.reserve(roles.size()); - for (const auto &role : roles) { - rolenames.emplace_back(role.rolename()); - } - return rolenames; - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - std::optional<std::string> GetRolenameForUser(const std::string &username) override { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - try { - auto locked_auth = auth_->ReadLock(); - auto user = locked_auth->GetUser(username); - if (!user) { - throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username); - } - - if (const auto *role = user->role(); role != nullptr) { - return role->rolename(); - } - return std::nullopt; - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - std::vector<memgraph::query::TypedValue> GetUsernamesForRole(const std::string &rolename) override { - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } - try { - auto locked_auth = auth_->ReadLock(); - auto role = locked_auth->GetRole(rolename); - if (!role) { - throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist.", rolename); - } - std::vector<memgraph::query::TypedValue> usernames; - const auto &users = locked_auth->AllUsersForRole(rolename); - usernames.reserve(users.size()); - for (const auto &user : users) { - usernames.emplace_back(user.username()); - } - return usernames; - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - void SetRole(const std::string &username, const std::string &rolename) override { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - if (!std::regex_match(rolename, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid role name."); - } - try { - auto locked_auth = auth_->Lock(); - auto user = locked_auth->GetUser(username); - if (!user) { - throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username); - } - auto role = locked_auth->GetRole(rolename); - if (!role) { - throw memgraph::query::QueryRuntimeException("Role '{}' doesn't exist .", rolename); - } - if (const auto *current_role = user->role(); current_role != nullptr) { - throw memgraph::query::QueryRuntimeException("User '{}' is already a member of role '{}'.", username, - current_role->rolename()); - } - user->SetRole(*role); - locked_auth->SaveUser(*user); - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - void ClearRole(const std::string &username) override { - if (!std::regex_match(username, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user name."); - } - try { - auto locked_auth = auth_->Lock(); - auto user = locked_auth->GetUser(username); - if (!user) { - throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username); - } - user->ClearRole(); - locked_auth->SaveUser(*user); - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - std::vector<std::vector<memgraph::query::TypedValue>> GetPrivileges(const std::string &user_or_role) override { - if (!std::regex_match(user_or_role, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user or role name."); - } - try { - auto locked_auth = auth_->ReadLock(); - std::vector<std::vector<memgraph::query::TypedValue>> grants; - auto user = locked_auth->GetUser(user_or_role); - auto role = locked_auth->GetRole(user_or_role); - if (!user && !role) { - throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role); - } - if (user) { - const auto &permissions = user->GetPermissions(); - for (const auto &privilege : memgraph::query::kPrivilegesAll) { - auto permission = memgraph::glue::PrivilegeToPermission(privilege); - auto effective = permissions.Has(permission); - if (permissions.Has(permission) != memgraph::auth::PermissionLevel::NEUTRAL) { - std::vector<std::string> description; - auto user_level = user->permissions().Has(permission); - if (user_level == memgraph::auth::PermissionLevel::GRANT) { - description.emplace_back("GRANTED TO USER"); - } else if (user_level == memgraph::auth::PermissionLevel::DENY) { - description.emplace_back("DENIED TO USER"); - } - if (const auto *role = user->role(); role != nullptr) { - auto role_level = role->permissions().Has(permission); - if (role_level == memgraph::auth::PermissionLevel::GRANT) { - description.emplace_back("GRANTED TO ROLE"); - } else if (role_level == memgraph::auth::PermissionLevel::DENY) { - description.emplace_back("DENIED TO ROLE"); - } - } - grants.push_back({memgraph::query::TypedValue(memgraph::auth::PermissionToString(permission)), - memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(effective)), - memgraph::query::TypedValue(memgraph::utils::Join(description, ", "))}); - } - } - } else { - const auto &permissions = role->permissions(); - for (const auto &privilege : memgraph::query::kPrivilegesAll) { - auto permission = memgraph::glue::PrivilegeToPermission(privilege); - auto effective = permissions.Has(permission); - if (effective != memgraph::auth::PermissionLevel::NEUTRAL) { - std::string description; - if (effective == memgraph::auth::PermissionLevel::GRANT) { - description = "GRANTED TO ROLE"; - } else if (effective == memgraph::auth::PermissionLevel::DENY) { - description = "DENIED TO ROLE"; - } - grants.push_back({memgraph::query::TypedValue(memgraph::auth::PermissionToString(permission)), - memgraph::query::TypedValue(memgraph::auth::PermissionLevelToString(effective)), - memgraph::query::TypedValue(description)}); - } - } - } - return grants; - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } - - void GrantPrivilege( - const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &label_privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &edge_type_privileges) override { - EditPermissions( - user_or_role, privileges, label_privileges, edge_type_privileges, - [](auto &permissions, const auto &permission) { - // TODO (mferencevic): should we first check that the - // privilege is granted/denied/revoked before - // unconditionally granting/denying/revoking it? - permissions.Grant(permission); - }, - [](auto &fine_grained_permissions, const auto &privilege_collection) { - for (const auto &[privilege, entities] : privilege_collection) { - const auto &permission = memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(privilege); - for (const auto &entity : entities) { - fine_grained_permissions.Grant(entity, permission); - } - } - }); - } - - void DenyPrivilege( - const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &label_privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &edge_type_privileges) override { - EditPermissions( - user_or_role, privileges, label_privileges, edge_type_privileges, - [](auto &permissions, const auto &permission) { - // TODO (mferencevic): should we first check that the - // privilege is granted/denied/revoked before - // unconditionally granting/denying/revoking it? - permissions.Deny(permission); - }, - [](auto &fine_grained_permissions, const auto &privilege_collection) { - for (const auto &[privilege, entities] : privilege_collection) { - const auto &permission = memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(privilege); - for (const auto &entity : entities) { - fine_grained_permissions.Deny(entity, permission); - } - } - }); - } - - void RevokePrivilege( - const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &label_privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &edge_type_privileges) override { - EditPermissions( - user_or_role, privileges, label_privileges, edge_type_privileges, - [](auto &permissions, const auto &permission) { - // TODO (mferencevic): should we first check that the - // privilege is granted/denied/revoked before - // unconditionally granting/denying/revoking it? - permissions.Revoke(permission); - }, - [](auto &fine_grained_permissions, const auto &privilege_collection) { - for ([[maybe_unused]] const auto &[privilege, entities] : privilege_collection) { - for (const auto &entity : entities) { - fine_grained_permissions.Revoke(entity); - } - } - }); - } - - private: - template <class TEditPermissionsFun, class TEditFineGrainedPermissionsFun> - void EditPermissions( - const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &label_privileges, - const std::vector<std::unordered_map<memgraph::query::AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> - &edge_type_privileges, - const TEditPermissionsFun &edit_permissions_fun, - const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun) { - if (!std::regex_match(user_or_role, name_regex_)) { - throw memgraph::query::QueryRuntimeException("Invalid user or role name."); - } - try { - std::vector<memgraph::auth::Permission> permissions; - permissions.reserve(privileges.size()); - for (const auto &privilege : privileges) { - permissions.push_back(memgraph::glue::PrivilegeToPermission(privilege)); - } - auto locked_auth = auth_->Lock(); - auto user = locked_auth->GetUser(user_or_role); - auto role = locked_auth->GetRole(user_or_role); - if (!user && !role) { - throw memgraph::query::QueryRuntimeException("User or role '{}' doesn't exist.", user_or_role); - } - if (user) { - for (const auto &permission : permissions) { - edit_permissions_fun(user->permissions(), permission); - } - for (const auto &label_privilege_collection : label_privileges) { - edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), - label_privilege_collection); - } - for (const auto &edge_type_privilege_collection : edge_type_privileges) { - edit_fine_grained_permissions_fun(user->fine_grained_access_handler().edge_type_permissions(), - edge_type_privilege_collection); - } - - locked_auth->SaveUser(*user); - } else { - for (const auto &permission : permissions) { - edit_permissions_fun(role->permissions(), permission); - } - for (const auto &label_privilege : label_privileges) { - edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), label_privilege); - } - for (const auto &edge_type_privilege : edge_type_privileges) { - edit_fine_grained_permissions_fun(role->fine_grained_access_handler().edge_type_permissions(), - edge_type_privilege); - } - - locked_auth->SaveRole(*role); - } - } catch (const memgraph::auth::AuthException &e) { - throw memgraph::query::QueryRuntimeException(e.what()); - } - } -}; - class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream, memgraph::communication::v2::OutputStream> { public: @@ -1277,7 +864,7 @@ int main(int argc, char **argv) { memgraph::query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories, FLAGS_data_directory); memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories(); - AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex); + memgraph::glue::AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex); memgraph::glue::AuthChecker auth_checker{&auth}; interpreter_context.auth = &auth_handler; interpreter_context.auth_checker = &auth_checker; diff --git a/tests/e2e/lba_procedures/CMakeLists.txt b/tests/e2e/lba_procedures/CMakeLists.txt index 485b51cbe..f1d4e852b 100644 --- a/tests/e2e/lba_procedures/CMakeLists.txt +++ b/tests/e2e/lba_procedures/CMakeLists.txt @@ -4,5 +4,6 @@ endfunction() copy_lba_procedures_e2e_python_files(common.py) copy_lba_procedures_e2e_python_files(lba_procedures.py) +copy_lba_procedures_e2e_python_files(show_privileges.py) add_subdirectory(procedures) diff --git a/tests/e2e/lba_procedures/show_privileges.py b/tests/e2e/lba_procedures/show_privileges.py new file mode 100644 index 000000000..ddd84d065 --- /dev/null +++ b/tests/e2e/lba_procedures/show_privileges.py @@ -0,0 +1,117 @@ +# Copyright 2022 Memgraph Ltd. +# +# Use of this software is governed by the Business Source License +# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +# License, and you may not use this file except in compliance with the Business Source License. +# +# As of the Change Date specified in that file, in accordance with +# the Business Source License, use of this software will be governed +# by the Apache License, Version 2.0, included in the file +# licenses/APL.txt. + +import sys +import pytest +from common import connect, execute_and_fetch_all + +BASIC_PRIVILEGES = [ + "CREATE", + "DELETE", + "MATCH", + "MERGE", + "SET", + "REMOVE", + "INDEX", + "STATS", + "AUTH", + "REPLICATION", + "READ_FILE", + "DURABILITY", + "FREE_MEMORY", + "TRIGGER", + "STREAM", + "CONFIG", + "CONSTRAINT", + "DUMP", + "MODULE_READ", + "WEBSOCKET", + "MODULE_WRITE", +] + + +def test_lba_procedures_show_privileges_first_user(): + expected_assertions_josip = [ + ("ALL LABELS", "CREATE_DELETE", "GLOBAL LABEL PERMISSION GRANTED TO USER"), + ( + "ALL EDGE_TYPES", + "CREATE_DELETE", + "GLOBAL EDGE_TYPE PERMISSION GRANTED TO USER", + ), + ("LABEL :Label1", "READ", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label2", "NO_PERMISSION", "LABEL PERMISSION DENIED TO USER"), + ("LABEL :Label3", "UPDATE", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label4", "READ", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label5", "CREATE_DELETE", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label6", "UPDATE", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label7", "NO_PERMISSION", "LABEL PERMISSION DENIED TO USER"), + ] + + cursor = connect(username="Josip", password="").cursor() + result = execute_and_fetch_all(cursor, "SHOW PRIVILEGES FOR Josip;") + + assert len(result) == 30 + + fine_privilege_results = [res for res in result if res[0] not in BASIC_PRIVILEGES] + + assert len(fine_privilege_results) == len(expected_assertions_josip) + assert set(expected_assertions_josip) == set(fine_privilege_results) + + +def test_lba_procedures_show_privileges_second_user(): + expected_assertions_boris = [ + ("AUTH", "GRANT", "GRANTED TO USER"), + ("LABEL :Label1", "READ", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label2", "NO_PERMISSION", "LABEL PERMISSION DENIED TO USER"), + ("LABEL :Label3", "UPDATE", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label4", "READ", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label5", "CREATE_DELETE", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label6", "UPDATE", "LABEL PERMISSION GRANTED TO USER"), + ("LABEL :Label7", "NO_PERMISSION", "LABEL PERMISSION DENIED TO USER"), + ] + + cursor = connect(username="Boris", password="").cursor() + result = execute_and_fetch_all(cursor, "SHOW PRIVILEGES FOR Boris;") + + assert len(result) == len(expected_assertions_boris) + assert set(result) == set(expected_assertions_boris) + + +def test_lba_procedures_show_privileges_third_user(): + expected_assertions_niko = [ + ("AUTH", "GRANT", "GRANTED TO USER"), + ("ALL LABELS", "READ", "GLOBAL LABEL PERMISSION GRANTED TO USER"), + ] + + cursor = connect(username="Niko", password="").cursor() + result = execute_and_fetch_all(cursor, "SHOW PRIVILEGES FOR Niko;") + + assert len(result) == len(expected_assertions_niko) + assert set(result) == set(expected_assertions_niko) + + +def test_lba_procedures_show_privileges_fourth_user(): + expected_assertions_bruno = [ + ("AUTH", "GRANT", "GRANTED TO USER"), + ("ALL LABELS", "UPDATE", "GLOBAL LABEL PERMISSION GRANTED TO USER"), + ] + + # TODO: Revisit behaviour of this test + + cursor = connect(username="Bruno", password="").cursor() + result = execute_and_fetch_all(cursor, "SHOW PRIVILEGES FOR Bruno;") + + assert len(result) == len(expected_assertions_bruno) + assert set(result) == set(expected_assertions_bruno) + + +if __name__ == "__main__": + sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/lba_procedures/workloads.yaml b/tests/e2e/lba_procedures/workloads.yaml index 046a28397..353001d5f 100644 --- a/tests/e2e/lba_procedures/workloads.yaml +++ b/tests/e2e/lba_procedures/workloads.yaml @@ -20,9 +20,52 @@ template_cluster: &template_cluster ] validation_queries: [] +show_privileges_cluster: &show_privileges_cluster + cluster: + main: + args: ["--bolt-port", "7687", "--log-level=TRACE"] + log_file: "lba-e2e.log" + setup_queries: [ + "Create User Josip;", + "Grant Read On Labels :Label1 to Josip;", + "Deny Read On Labels :Label2 to Josip;", + "Grant Update On Labels :Label3 to Josip;", + "Deny Update On Labels :Label4 to Josip;", + "Grant Create_Delete On Labels :Label5 to Josip;", + "Deny Create_Delete On Labels :Label6 to Josip;", + "Grant Create_Delete On Labels :Label7 to Josip;", + "Deny Read On Labels :Label7 to Josip;", + + "Create User Boris;", + "Grant Auth to Boris;", + "Grant Read On Labels :Label1 to Boris;", + "Deny Read On Labels :Label2 to Boris;", + "Grant Update On Labels :Label3 to Boris;", + "Deny Update On Labels :Label4 to Boris;", + "Grant Create_Delete On Labels :Label5 to Boris;", + "Deny Create_Delete On Labels :Label6 to Boris;", + "Grant Create_Delete On Labels :Label7 to Boris;", + "Deny Read On Labels :Label7 to Boris;", + + "Create User Niko;", + "Grant Auth to Niko;", + "Grant Create_Delete On Labels * to Niko", + "Deny Update On Labels * to Niko", + + "Create User Bruno;", + "Grant Auth to Bruno;", + "Deny Create_Delete On Labels * to Bruno" + ] + workloads: - name: "Label-based auth" binary: "tests/e2e/pytest_runner.sh" proc: "tests/e2e/lba_procedures/procedures/" args: ["lba_procedures/lba_procedures.py"] <<: *template_cluster + + - name: "show-privileges" + binary: "tests/e2e/pytest_runner.sh" + proc: "tests/e2e/lba_procedures/procedures/" + args: ["lba_procedures/show_privileges.py"] + <<: *show_privileges_cluster diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a474ff3de..420406c4e 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -324,6 +324,9 @@ target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2 add_unit_test(auth_checker.cpp) target_link_libraries(${test_prefix}auth_checker mg-glue mg-auth) +add_unit_test(auth_handler.cpp) +target_link_libraries(${test_prefix}auth_handler mg-glue mg-auth) + # Test mg-auth if(MG_ENTERPRISE) add_unit_test(auth.cpp) diff --git a/tests/unit/auth_handler.cpp b/tests/unit/auth_handler.cpp new file mode 100644 index 000000000..06c69c7e9 --- /dev/null +++ b/tests/unit/auth_handler.cpp @@ -0,0 +1,724 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <gflags/gflags.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "auth/auth.hpp" +#include "auth/models.hpp" +#include "glue/auth_handler.hpp" +#include "query/typed_value.hpp" +#include "utils/file.hpp" +#include "utils/rw_lock.hpp" +#include "utils/synchronized.hpp" + +class AuthQueryHandlerFixture : public testing::Test { + protected: + std::filesystem::path test_folder_{std::filesystem::temp_directory_path() / "MG_tests_unit_auth_handler"}; + memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> auth{ + test_folder_ / ("unit_auth_handler_test_" + std::to_string(static_cast<int>(getpid())))}; + memgraph::glue::AuthQueryHandler auth_handler{&auth, memgraph::glue::kDefaultUserRoleRegex.data()}; + + std::string user_name = "Mate"; + std::string edge_type_repr = "EdgeType1"; + std::string label_repr = "Label1"; + memgraph::auth::Permissions perms{}; + memgraph::auth::FineGrainedAccessHandler handler{}; + + virtual void SetUp() { + memgraph::utils::EnsureDir(test_folder_); + memgraph::utils::license::global_license_checker.EnableTesting(); + } + + virtual void TearDown() { + std::filesystem::remove_all(test_folder_); + perms = memgraph::auth::Permissions{}; + handler = memgraph::auth::FineGrainedAccessHandler{}; + } +}; + +TEST_F(AuthQueryHandlerFixture, GivenAuthQueryHandlerWhenInitializedHaveNoUsernamesOrRolenames) { + ASSERT_EQ(auth_handler.GetUsernames().size(), 0); + ASSERT_EQ(auth_handler.GetRolenames().size(), 0); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenNoDeniesOrGrantsThenNothingIsReturned) { + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + { ASSERT_EQ(auth_handler.GetUsernames().size(), 1); } + + { + auto privileges = auth_handler.GetPrivileges(user_name); + + ASSERT_EQ(privileges.size(), 0); + } +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedGrantPermissionThenItIsReturned) { + perms.Grant(memgraph::auth::Permission::MATCH); + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "GRANT"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenAddedDenyPermissionThenItIsReturned) { + perms.Deny(memgraph::auth::Permission::MATCH); + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "DENY"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "DENIED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenPrivilegeRevokedThenNothingIsReturned) { + perms.Deny(memgraph::auth::Permission::MATCH); + perms.Revoke(memgraph::auth::Permission::MATCH); + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 0); +} + +TEST_F(AuthQueryHandlerFixture, GivenRoleWhenPrivilegeGrantedThenItIsReturned) { + perms.Grant(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms, handler}; + auth->SaveRole(role); + + { ASSERT_EQ(auth_handler.GetRolenames().size(), 1); } + + { + auto privileges = auth_handler.GetPrivileges("Mates_role"); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "GRANT"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GRANTED TO ROLE"); + } +} + +TEST_F(AuthQueryHandlerFixture, GivenRoleWhenPrivilegeDeniedThenItIsReturned) { + perms.Deny(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms, handler}; + auth->SaveRole(role); + + auto privileges = auth_handler.GetPrivileges("Mates_role"); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "DENY"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "DENIED TO ROLE"); +} + +TEST_F(AuthQueryHandlerFixture, GivenRoleWhenPrivilegeRevokedThenNothingIsReturned) { + perms.Deny(memgraph::auth::Permission::MATCH); + perms.Revoke(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms, handler}; + auth->SaveRole(role); + + auto privileges = auth_handler.GetPrivileges("Mates_role"); + ASSERT_EQ(privileges.size(), 0); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedTwoPrivilegesThenBothAreReturned) { + perms.Grant(memgraph::auth::Permission::MATCH); + perms.Grant(memgraph::auth::Permission::CREATE); + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 2); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherGrantedThenBothArePrinted) { + perms.Grant(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms, handler}; + auth->SaveRole(role); + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + user.SetRole(role); + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "GRANT"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GRANTED TO USER, GRANTED TO ROLE"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherDeniedThenBothArePrinted) { + perms.Deny(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", perms, handler}; + auth->SaveRole(role); + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + user.SetRole(role); + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "DENY"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "DENIED TO USER, DENIED TO ROLE"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneGrantedAndOtherDeniedThenBothArePrinted) { + memgraph::auth::Permissions role_perms{}; + role_perms.Deny(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", role_perms, handler}; + auth->SaveRole(role); + + memgraph::auth::Permissions user_perms{}; + user_perms.Grant(memgraph::auth::Permission::MATCH); + memgraph::auth::User user = memgraph::auth::User{user_name, "", user_perms, handler}; + user.SetRole(role); + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "DENY"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GRANTED TO USER, DENIED TO ROLE"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserAndRoleWhenOneDeniedAndOtherGrantedThenBothArePrinted) { + memgraph::auth::Permissions role_perms{}; + role_perms.Grant(memgraph::auth::Permission::MATCH); + memgraph::auth::Role role = memgraph::auth::Role{"Mates_role", role_perms, handler}; + auth->SaveRole(role); + + memgraph::auth::Permissions user_perms{}; + user_perms.Deny(memgraph::auth::Permission::MATCH); + memgraph::auth::User user = memgraph::auth::User{user_name, "", user_perms, handler}; + user.SetRole(role); + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "MATCH"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "DENY"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "DENIED TO USER, GRANTED TO ROLE"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnLabelThenIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::READ); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "LABEL :Label1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "READ"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "LABEL PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnLabelThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::UPDATE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "LABEL :Label1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "UPDATE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "LABEL PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnLabelThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::UPDATE); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::CREATE_DELETE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "LABEL :Label1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "CREATE_DELETE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "LABEL PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnLabelThenIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::READ); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "ALL LABELS"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "READ"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GLOBAL LABEL PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnLabelThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "ALL LABELS"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "UPDATE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GLOBAL LABEL PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnLabelThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "ALL LABELS"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "CREATE_DELETE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GLOBAL LABEL PERMISSION GRANTED TO USER"); +} + +// EDGE_TYPES +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedPrivilegeOnEdgeTypeThenIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::READ); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "EDGE_TYPE :EdgeType1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "READ"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "EDGE_TYPE PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedMultiplePrivilegesOnEdgeTypeThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::UPDATE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "EDGE_TYPE :EdgeType1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "UPDATE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "EDGE_TYPE PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAllPrivilegesOnEdgeTypeThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::UPDATE); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::CREATE_DELETE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "EDGE_TYPE :EdgeType1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "CREATE_DELETE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "EDGE_TYPE PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalPrivilegeOnEdgeTypeThenIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::READ); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "ALL EDGE_TYPES"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "READ"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GLOBAL EDGE_TYPE PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalMultiplePrivilegesOnEdgeTypeThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "ALL EDGE_TYPES"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "UPDATE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GLOBAL EDGE_TYPE PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedGlobalAllPrivilegesOnEdgeTypeThenTopOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::READ); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::UPDATE); + read_permission.Grant("*", memgraph::auth::FineGrainedPermission::CREATE_DELETE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "ALL EDGE_TYPES"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "CREATE_DELETE"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "GLOBAL EDGE_TYPE PERMISSION GRANTED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnLabelThenNoPermission) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(label_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Deny(label_repr, memgraph::auth::FineGrainedPermission::READ); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + memgraph::auth::FineGrainedAccessPermissions{}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "LABEL :Label1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "NO_PERMISSION"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "LABEL PERMISSION DENIED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedAndDeniedOnEdgeTypeThenNoPermission) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Deny(edge_type_repr, memgraph::auth::FineGrainedPermission::READ); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "EDGE_TYPE :EdgeType1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "NO_PERMISSION"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "EDGE_TYPE PERMISSION DENIED TO USER"); +} + +TEST_F(AuthQueryHandlerFixture, GivenUserWhenGrantedReadAndDeniedUpdateThenOneIsDisplayed) { + auto read_permission = memgraph::auth::FineGrainedAccessPermissions(); + read_permission.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::READ); + read_permission.Deny(edge_type_repr, memgraph::auth::FineGrainedPermission::UPDATE); + + handler = memgraph::auth::FineGrainedAccessHandler{ + memgraph::auth::FineGrainedAccessPermissions{}, + memgraph::auth::FineGrainedAccessPermissions{read_permission}, + }; + + memgraph::auth::User user = memgraph::auth::User{user_name, "", perms, handler}; + auth->SaveUser(user); + + auto privileges = auth_handler.GetPrivileges(user_name); + ASSERT_EQ(privileges.size(), 1); + + auto result = *privileges.begin(); + ASSERT_EQ(result.size(), 3); + + ASSERT_TRUE(result[0].IsString()); + ASSERT_EQ(result[0].ValueString(), "EDGE_TYPE :EdgeType1"); + + ASSERT_TRUE(result[1].IsString()); + ASSERT_EQ(result[1].ValueString(), "READ"); + + ASSERT_TRUE(result[2].IsString()); + ASSERT_EQ(result[2].ValueString(), "EDGE_TYPE PERMISSION GRANTED TO USER"); +}