// Copyright 2023 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 #include #include "auth/models.hpp" #include "glue/auth.hpp" #include "license/license.hpp" #include "query/constants.hpp" namespace { struct PermissionForPrivilegeResult { std::string permission; memgraph::auth::PermissionLevel permission_level; std::string description; }; struct FineGrainedPermissionForPrivilegeResult { std::string permission; #ifdef MG_ENTERPRISE memgraph::auth::FineGrainedPermission permission_level; #endif 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> ConstructPrivilegesResult( const std::vector &privileges) { std::vector> 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> ShowUserPrivileges( const std::optional &user) { std::vector 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 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> ShowRolePrivileges( const std::optional &role) { std::vector 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); } #ifdef MG_ENTERPRISE std::vector GetFineGrainedPermissionForPrivilegeForUserOrRole( const memgraph::auth::FineGrainedAccessPermissions &permissions, const std::string &permission_type, const std::string &user_or_role) { std::vector fine_grained_permissions; if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) { return 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::NOTHING ? "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::NOTHING ? "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> ConstructFineGrainedPrivilegesResult( const std::vector &privileges) { std::vector> grants; if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) { return {}; } 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> ShowFineGrainedUserPrivileges( const std::optional &user) { if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) { return {}; } 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> ShowFineGrainedRolePrivileges( const std::optional &role) { if (!memgraph::license::global_license_checker.IsEnterpriseValidFast()) { return {}; } 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); } #endif } // namespace namespace memgraph::glue { AuthQueryHandler::AuthQueryHandler( memgraph::utils::Synchronized *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 &password) { if (name_regex_string_ != kDefaultUserRoleRegex) { if (const auto license_check_result = memgraph::license::global_license_checker.IsEnterpriseValid(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::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 #ifdef MG_ENTERPRISE , {{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::query::kAsterisk}}}}, { { { memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, { memgraph::query::kAsterisk } } } } #endif ); } 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 &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 AuthQueryHandler::GetUsernames() { try { auto locked_auth = auth_->ReadLock(); std::vector 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 AuthQueryHandler::GetRolenames() { try { auto locked_auth = auth_->ReadLock(); std::vector 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 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 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 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> 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> grants; #ifdef MG_ENTERPRISE std::vector> fine_grained_grants; #endif 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); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { fine_grained_grants = ShowFineGrainedUserPrivileges(user); } #endif } else { grants = ShowRolePrivileges(role); #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { fine_grained_grants = ShowFineGrainedRolePrivileges(role); } #endif } #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { grants.insert(grants.end(), fine_grained_grants.begin(), fine_grained_grants.end()); } #endif 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 &privileges #ifdef MG_ENTERPRISE , const std::vector>> &label_privileges, const std::vector>> &edge_type_privileges #endif ) { EditPermissions( user_or_role, privileges, #ifdef MG_ENTERPRISE label_privileges, edge_type_privileges, #endif [](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); } #ifdef MG_ENTERPRISE , [](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); } } } #endif ); } // namespace memgraph::glue void AuthQueryHandler::DenyPrivilege(const std::string &user_or_role, const std::vector &privileges) { EditPermissions( user_or_role, privileges, #ifdef MG_ENTERPRISE {}, {}, #endif [](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); } #ifdef MG_ENTERPRISE , [](auto &fine_grained_permissions, const auto &privilege_collection) {} #endif ); } void AuthQueryHandler::RevokePrivilege( const std::string &user_or_role, const std::vector &privileges #ifdef MG_ENTERPRISE , const std::vector>> &label_privileges, const std::vector>> &edge_type_privileges #endif ) { EditPermissions( user_or_role, privileges, #ifdef MG_ENTERPRISE label_privileges, edge_type_privileges, #endif [](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); } #ifdef MG_ENTERPRISE , [](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); } } } #endif ); } // namespace memgraph::glue template void AuthQueryHandler::EditPermissions( const std::string &user_or_role, const std::vector &privileges #ifdef MG_ENTERPRISE , const std::vector>> &label_privileges, const std::vector>> &edge_type_privileges #endif , const TEditPermissionsFun &edit_permissions_fun #ifdef MG_ENTERPRISE , const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun #endif ) { if (!std::regex_match(user_or_role, name_regex_)) { throw memgraph::query::QueryRuntimeException("Invalid user or role name."); } try { std::vector 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); } #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { 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); } } #endif locked_auth->SaveUser(*user); } else { for (const auto &permission : permissions) { edit_permissions_fun(role->permissions(), permission); } #ifdef MG_ENTERPRISE if (memgraph::license::global_license_checker.IsEnterpriseValidFast()) { for (const auto &label_privilege : label_privileges) { edit_fine_grained_permissions_fun(role->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); } } #endif locked_auth->SaveRole(*role); } } catch (const memgraph::auth::AuthException &e) { throw memgraph::query::QueryRuntimeException(e.what()); } } } // namespace memgraph::glue