From b489ac7cff6e3d4ceca0603d4aa94a771e120401 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Boris=20Ta=C5=A1evski?=
 <36607228+BorisTasevski@users.noreply.github.com>
Date: Thu, 18 Aug 2022 16:59:38 +0200
Subject: [PATCH] [E129-MG < T1003-MG] Expand fine grained access checker with
 more granular permissions (#496)

* Added enum for more granular access control; Expanded functionality of fine grained access checker; Propagated changes to Edit, Deny and Revoke permissions methods in interpreter

* Introduced Merge method for merging two colle with permissions

* e2e tests implementation started

* FineGrainedAccessChecker Grant and Deny methods reworked

* removed faulty test addition

* Naming fixes; FineGrainedAccessChecker unit tests introduced

* unnecessary includes removed; minor code improvements

* Access checker reworked; denies and grant merged into single permission object; Created global_permission that applies to all non-created permissions. Grant, Deny and Revoke reworked; Merge method reworked

* Fixed wrong check;

* PR review changes; Naming and const fixed, replaced double tertiary with lambda

* unwrapping the iterator fix

* minor spelling fixes
---
 src/auth/models.cpp                           | 208 +++++----
 src/auth/models.hpp                           |  58 ++-
 src/glue/auth_checker.cpp                     |   7 +-
 src/memgraph.cpp                              |  72 ++--
 tests/e2e/write_procedures/procedures/read.py |   3 +-
 tests/unit/auth.cpp                           | 396 +++++++++++++++++-
 tests/unit/auth_checker.cpp                   |  34 +-
 tests/unit/query_plan_match_filter_return.cpp |  11 +-
 8 files changed, 591 insertions(+), 198 deletions(-)

diff --git a/src/auth/models.cpp b/src/auth/models.cpp
index 4733ee201..2753b7df5 100644
--- a/src/auth/models.cpp
+++ b/src/auth/models.cpp
@@ -8,10 +8,8 @@
 
 #include "auth/models.hpp"
 
-#include <algorithm>
-#include <iterator>
+#include <cstdint>
 #include <regex>
-#include <unordered_set>
 
 #include <gflags/gflags.h>
 
@@ -19,6 +17,7 @@
 #include "auth/exceptions.hpp"
 #include "utils/cast.hpp"
 #include "utils/license.hpp"
+#include "utils/logging.hpp"
 #include "utils/settings.hpp"
 #include "utils/string.hpp"
 
@@ -101,6 +100,24 @@ std::string PermissionLevelToString(PermissionLevel level) {
   }
 }
 
+FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
+                                   const FineGrainedAccessPermissions &second) {
+  std::unordered_map<std::string, uint64_t> permissions{first.GetPermissions()};
+  std::optional<uint64_t> global_permission;
+
+  if (second.GetGlobalPermission().has_value()) {
+    global_permission = second.GetGlobalPermission().value();
+  } else if (first.GetGlobalPermission().has_value()) {
+    global_permission = first.GetGlobalPermission().value();
+  }
+
+  for (const auto &[label_name, permission] : second.GetPermissions()) {
+    permissions[label_name] = permission;
+  }
+
+  return FineGrainedAccessPermissions(permissions, global_permission);
+}
+
 Permissions::Permissions(uint64_t grants, uint64_t denies) {
   // The deny bitmask has higher priority than the grant bitmask.
   denies_ = denies;
@@ -186,96 +203,58 @@ bool operator==(const Permissions &first, const Permissions &second) {
 
 bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
 
-const std::string ASTERISK = "*";
+FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions,
+                                                           const std::optional<uint64_t> &global_permission)
+    : permissions_(permissions), global_permission_(global_permission) {}
 
-FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants,
-                                                           const std::unordered_set<std::string> &denies)
-    : grants_(grants), denies_(denies) {}
+PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission,
+                                                  const LabelPermission label_permission) const {
+  const auto concrete_permission = std::invoke([&]() -> uint64_t {
+    if (permissions_.contains(permission)) {
+      return permissions_.at(permission);
+    }
 
-PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission) const {
-  if ((denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) || denies_.find(permission) != denies_.end()) {
-    return PermissionLevel::DENY;
-  }
+    if (global_permission_.has_value()) {
+      return global_permission_.value();
+    }
 
-  if ((grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) || grants_.find(permission) != denies_.end()) {
-    return PermissionLevel::GRANT;
-  }
+    return 0;
+  });
 
-  return PermissionLevel::NEUTRAL;
+  const auto temp_permission = concrete_permission & label_permission;
+
+  return temp_permission > 0 ? PermissionLevel::GRANT : PermissionLevel::DENY;
 }
 
-void FineGrainedAccessPermissions::Grant(const std::string &permission) {
+void FineGrainedAccessPermissions::Grant(const std::string &permission, const LabelPermission label_permission) {
   if (permission == ASTERISK) {
-    grants_.clear();
-    denies_.clear();
-    grants_.insert(permission);
-
-    return;
-  }
-
-  auto deniedPermissionIter = denies_.find(permission);
-
-  if (deniedPermissionIter != denies_.end()) {
-    denies_.erase(deniedPermissionIter);
-  }
-
-  if (grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) {
-    grants_.erase(ASTERISK);
-  }
-
-  if (grants_.find(permission) == grants_.end()) {
-    grants_.insert(permission);
+    global_permission_ = CalculateGrant(label_permission);
+  } else {
+    permissions_[permission] |= CalculateGrant(label_permission);
   }
 }
 
 void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
   if (permission == ASTERISK) {
-    grants_.clear();
-    denies_.clear();
-
-    return;
-  }
-
-  auto deniedPermissionIter = denies_.find(permission);
-  auto grantedPermissionIter = grants_.find(permission);
-
-  if (deniedPermissionIter != denies_.end()) {
-    denies_.erase(deniedPermissionIter);
-  }
-
-  if (grantedPermissionIter != grants_.end()) {
-    grants_.erase(grantedPermissionIter);
+    permissions_.clear();
+    global_permission_ = std::nullopt;
+  } else {
+    permissions_.erase(permission);
   }
 }
 
-void FineGrainedAccessPermissions::Deny(const std::string &permission) {
+void FineGrainedAccessPermissions::Deny(const std::string &permission, const LabelPermission label_permission) {
   if (permission == ASTERISK) {
-    grants_.clear();
-    denies_.clear();
-    denies_.insert(permission);
-
-    return;
-  }
-
-  auto grantedPermissionIter = grants_.find(permission);
-
-  if (grantedPermissionIter != grants_.end()) {
-    grants_.erase(grantedPermissionIter);
-  }
-
-  if (denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) {
-    denies_.erase(ASTERISK);
-  }
-
-  if (denies_.find(permission) == denies_.end()) {
-    denies_.insert(permission);
+    global_permission_ = CalculateDeny(label_permission);
+  } else {
+    permissions_[permission] = CalculateDeny(label_permission);
   }
 }
 
 nlohmann::json FineGrainedAccessPermissions::Serialize() const {
   nlohmann::json data = nlohmann::json::object();
-  data["grants"] = grants_;
-  data["denies"] = denies_;
+  data["permissions"] = permissions_;
+  data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
   return data;
 }
 
@@ -284,14 +263,51 @@ FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlo
     throw AuthException("Couldn't load permissions data!");
   }
 
-  return FineGrainedAccessPermissions(data["grants"], data["denies"]);
+  std::optional<uint64_t> global_permission;
+
+  if (data["global_permission"].empty() || data["global_permission"] == -1) {
+    global_permission = std::nullopt;
+  } else {
+    global_permission = data["global_permission"];
+  }
+
+  return FineGrainedAccessPermissions(data["permissions"], global_permission);
 }
 
-const std::unordered_set<std::string> &FineGrainedAccessPermissions::grants() const { return grants_; }
-const std::unordered_set<std::string> &FineGrainedAccessPermissions::denies() const { return denies_; }
+const std::unordered_map<std::string, uint64_t> &FineGrainedAccessPermissions::GetPermissions() const {
+  return permissions_;
+}
+const std::optional<uint64_t> &FineGrainedAccessPermissions::GetGlobalPermission() const { return global_permission_; };
+
+uint64_t FineGrainedAccessPermissions::CalculateGrant(LabelPermission label_permission) {
+  uint64_t shift{1};
+  uint64_t result{0};
+  auto uint_label_permission = static_cast<uint64_t>(label_permission);
+
+  while (uint_label_permission > 0) {
+    result |= uint_label_permission;
+    uint_label_permission >>= shift;
+  }
+
+  return result;
+}
+
+uint64_t FineGrainedAccessPermissions::CalculateDeny(LabelPermission label_permission) {
+  uint64_t shift{1};
+  uint64_t result{0};
+  auto uint_label_permission = static_cast<uint64_t>(label_permission);
+
+  while (uint_label_permission <= LabelPermissionMax) {
+    result |= uint_label_permission;
+    uint_label_permission <<= shift;
+  }
+
+  return LabelPermissionAll - result;
+}
 
 bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
-  return first.grants() == second.grants() && first.denies() == second.denies();
+  return first.GetPermissions() == second.GetPermissions() &&
+         first.GetGlobalPermission() == second.GetGlobalPermission();
 }
 
 bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
@@ -321,7 +337,7 @@ FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::j
   if (!data.is_object()) {
     throw AuthException("Couldn't load role data!");
   }
-  if (!data["label_permissions"].is_object() && !data["edge_type_permissions"].is_object()) {
+  if (!data["label_permissions"].is_object() || !data["edge_type_permissions"].is_object()) {
     throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
   }
   auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
@@ -439,46 +455,16 @@ Permissions User::GetPermissions() const {
 
 FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
   if (role_) {
-    std::unordered_set<std::string> resultGrants;
-
-    std::set_union(fine_grained_access_handler_.label_permissions().grants().begin(),
-                   fine_grained_access_handler_.label_permissions().grants().end(),
-                   role_->fine_grained_access_handler().label_permissions().grants().begin(),
-                   role_->fine_grained_access_handler().label_permissions().grants().end(),
-                   std::inserter(resultGrants, resultGrants.begin()));
-
-    std::unordered_set<std::string> resultDenies;
-
-    std::set_union(fine_grained_access_handler_.label_permissions().denies().begin(),
-                   fine_grained_access_handler_.label_permissions().denies().end(),
-                   role_->fine_grained_access_handler().label_permissions().denies().begin(),
-                   role_->fine_grained_access_handler().label_permissions().denies().end(),
-                   std::inserter(resultDenies, resultDenies.begin()));
-
-    return FineGrainedAccessPermissions(resultGrants, resultDenies);
+    return Merge(role()->fine_grained_access_handler().label_permissions(),
+                 fine_grained_access_handler_.label_permissions());
   }
   return fine_grained_access_handler_.label_permissions();
 }
 
 FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
   if (role_) {
-    std::unordered_set<std::string> resultGrants;
-
-    std::set_union(fine_grained_access_handler_.edge_type_permissions().grants().begin(),
-                   fine_grained_access_handler_.edge_type_permissions().grants().end(),
-                   role_->fine_grained_access_handler().edge_type_permissions().grants().begin(),
-                   role_->fine_grained_access_handler().edge_type_permissions().grants().end(),
-                   std::inserter(resultGrants, resultGrants.begin()));
-
-    std::unordered_set<std::string> resultDenies;
-
-    std::set_union(fine_grained_access_handler_.edge_type_permissions().denies().begin(),
-                   fine_grained_access_handler_.edge_type_permissions().denies().end(),
-                   role_->fine_grained_access_handler().edge_type_permissions().denies().begin(),
-                   role_->fine_grained_access_handler().edge_type_permissions().denies().end(),
-                   std::inserter(resultDenies, resultDenies.begin()));
-
-    return FineGrainedAccessPermissions(resultGrants, resultDenies);
+    return Merge(role()->fine_grained_access_handler().edge_type_permissions(),
+                 fine_grained_access_handler_.edge_type_permissions());
   }
   return fine_grained_access_handler_.edge_type_permissions();
 }
diff --git a/src/auth/models.hpp b/src/auth/models.hpp
index 85459b7a2..5556f4361 100644
--- a/src/auth/models.hpp
+++ b/src/auth/models.hpp
@@ -10,12 +10,12 @@
 
 #include <optional>
 #include <string>
-#include <unordered_set>
+#include <unordered_map>
 
 #include <json/json.hpp>
-#include <unordered_set>
 
 namespace memgraph::auth {
+const std::string ASTERISK = "*";
 // These permissions must have values that are applicable for usage in a
 // bitmask.
 // clang-format off
@@ -44,15 +44,34 @@ enum class Permission : uint64_t {
 };
 // clang-format on
 
+// clang-format off
+enum class LabelPermission : uint64_t {
+  READ          = 1,
+  EDIT          = 1U << 1U,
+  CREATE_DELETE = 1U << 2U
+};
+// clang-format on
+
+constexpr inline uint64_t operator|(LabelPermission lhs, LabelPermission rhs) {
+  return static_cast<uint64_t>(lhs) | static_cast<uint64_t>(rhs);
+}
+
+constexpr inline uint64_t operator|(uint64_t lhs, LabelPermission rhs) { return lhs | static_cast<uint64_t>(rhs); }
+
+constexpr inline uint64_t operator&(uint64_t lhs, LabelPermission rhs) {
+  return (lhs & static_cast<uint64_t>(rhs)) != 0;
+}
+
+constexpr uint64_t LabelPermissionAll = memgraph::auth::LabelPermission::CREATE_DELETE |
+                                        memgraph::auth::LabelPermission::EDIT | memgraph::auth::LabelPermission::READ;
+constexpr uint64_t LabelPermissionMax = static_cast<uint64_t>(memgraph::auth::LabelPermission::CREATE_DELETE);
+constexpr uint64_t LabelPermissionMin = static_cast<uint64_t>(memgraph::auth::LabelPermission::READ);
+
 // Function that converts a permission to its string representation.
 std::string PermissionToString(Permission permission);
 
 // Class that indicates what permission level the user/role has.
-enum class PermissionLevel {
-  GRANT,
-  NEUTRAL,
-  DENY,
-};
+enum class PermissionLevel : uint8_t { GRANT, NEUTRAL, DENY };
 
 // Function that converts a permission level to its string representation.
 std::string PermissionLevelToString(PermissionLevel level);
@@ -98,34 +117,36 @@ bool operator!=(const Permissions &first, const Permissions &second);
 
 class FineGrainedAccessPermissions final {
  public:
-  explicit FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants = {},
-                                        const std::unordered_set<std::string> &denies = {});
-
+  explicit FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions = {},
+                                        const std::optional<uint64_t> &global_permission = std::nullopt);
   FineGrainedAccessPermissions(const FineGrainedAccessPermissions &) = default;
   FineGrainedAccessPermissions &operator=(const FineGrainedAccessPermissions &) = default;
   FineGrainedAccessPermissions(FineGrainedAccessPermissions &&) = default;
   FineGrainedAccessPermissions &operator=(FineGrainedAccessPermissions &&) = default;
   ~FineGrainedAccessPermissions() = default;
 
-  PermissionLevel Has(const std::string &permission) const;
+  PermissionLevel Has(const std::string &permission, LabelPermission label_permission) const;
 
-  void Grant(const std::string &permission);
+  void Grant(const std::string &permission, LabelPermission label_permission);
 
   void Revoke(const std::string &permission);
 
-  void Deny(const std::string &permission);
+  void Deny(const std::string &permission, LabelPermission label_permission);
 
   nlohmann::json Serialize() const;
 
   /// @throw AuthException if unable to deserialize.
   static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
 
-  const std::unordered_set<std::string> &grants() const;
-  const std::unordered_set<std::string> &denies() const;
+  const std::unordered_map<std::string, uint64_t> &GetPermissions() const;
+  const std::optional<uint64_t> &GetGlobalPermission() const;
 
  private:
-  std::unordered_set<std::string> grants_{};
-  std::unordered_set<std::string> denies_{};
+  std::unordered_map<std::string, uint64_t> permissions_{};
+  std::optional<uint64_t> global_permission_;
+
+  static uint64_t CalculateGrant(LabelPermission label_permission);
+  static uint64_t CalculateDeny(LabelPermission label_permission);
 };
 
 bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
@@ -252,4 +273,7 @@ class User final {
 };
 
 bool operator==(const User &first, const User &second);
+
+FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
+                                   const FineGrainedAccessPermissions &second);
 }  // namespace memgraph::auth
diff --git a/src/glue/auth_checker.cpp b/src/glue/auth_checker.cpp
index d96ced174..b29561289 100644
--- a/src/glue/auth_checker.cpp
+++ b/src/glue/auth_checker.cpp
@@ -21,14 +21,15 @@ namespace {
 bool IsUserAuthorizedLabels(const memgraph::auth::User &user, const memgraph::query::DbAccessor &dba,
                             const std::vector<memgraph::storage::LabelId> &labels) {
   return std::all_of(labels.begin(), labels.end(), [&dba, &user](const auto label) {
-    return user.GetFineGrainedAccessLabelPermissions().Has(dba.LabelToName(label)) ==
-           memgraph::auth::PermissionLevel::GRANT;
+    return user.GetFineGrainedAccessLabelPermissions().Has(
+               dba.LabelToName(label), memgraph::auth::LabelPermission::READ) == memgraph::auth::PermissionLevel::GRANT;
   });
 }
 
 bool IsUserAuthorizedEdgeType(const memgraph::auth::User &user, const memgraph::query::DbAccessor &dba,
                               const memgraph::storage::EdgeTypeId &edgeType) {
-  return user.GetFineGrainedAccessEdgeTypePermissions().Has(dba.EdgeTypeToName(edgeType)) ==
+  return user.GetFineGrainedAccessEdgeTypePermissions().Has(dba.EdgeTypeToName(edgeType),
+                                                            memgraph::auth::LabelPermission::READ) ==
          memgraph::auth::PermissionLevel::GRANT;
 }
 }  // namespace
diff --git a/src/memgraph.cpp b/src/memgraph.cpp
index dde14004f..b611186d0 100644
--- a/src/memgraph.cpp
+++ b/src/memgraph.cpp
@@ -756,43 +756,57 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
 
   void GrantPrivilege(const std::string &user_or_role,
                       const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
-                      const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
-    EditPermissions(user_or_role, privileges, labels, edgeTypes, [](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);
-    });
+                      const std::vector<std::string> &labels, const std::vector<std::string> &edge_types) override {
+    EditPermissions(
+        user_or_role, privileges, labels, edge_types,
+        [](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 &label_permissions, const auto &label) {
+          label_permissions.Grant(label, memgraph::auth::LabelPermission::CREATE_DELETE);
+        });
   }
 
   void DenyPrivilege(const std::string &user_or_role,
                      const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
-                     const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
-    EditPermissions(user_or_role, privileges, labels, edgeTypes, [](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);
-    });
+                     const std::vector<std::string> &labels, const std::vector<std::string> &edge_types) override {
+    EditPermissions(
+        user_or_role, privileges, labels, edge_types,
+        [](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 &label_permissions, const auto &label) {
+          label_permissions.Deny(label, memgraph::auth::LabelPermission::READ);
+        });
   }
 
   void RevokePrivilege(const std::string &user_or_role,
                        const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
-                       const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
-    EditPermissions(user_or_role, privileges, labels, edgeTypes, [](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);
-    });
+                       const std::vector<std::string> &labels, const std::vector<std::string> &edge_types) override {
+    EditPermissions(
+        user_or_role, privileges, labels, edge_types,
+        [](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 &label_permissions, const auto &label) { label_permissions.Revoke(label); });
   }
 
  private:
-  template <class TEditFun>
+  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::string> &labels, const std::vector<std::string> &edgeTypes,
-                       const TEditFun &edit_fun) {
+                       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.");
     }
@@ -810,25 +824,25 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
       }
       if (user) {
         for (const auto &permission : permissions) {
-          edit_fun(&user->permissions(), permission);
+          edit_permissions_fun(user->permissions(), permission);
         }
         for (const auto &label : labels) {
-          edit_fun(&user->fine_grained_access_handler().label_permissions(), label);
+          edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), label);
         }
         for (const auto &edgeType : edgeTypes) {
-          edit_fun(&user->fine_grained_access_handler().edge_type_permissions(), edgeType);
+          edit_fine_grained_permissions_fun(user->fine_grained_access_handler().edge_type_permissions(), edgeType);
         }
 
         locked_auth->SaveUser(*user);
       } else {
         for (const auto &permission : permissions) {
-          edit_fun(&role->permissions(), permission);
+          edit_permissions_fun(role->permissions(), permission);
         }
         for (const auto &label : labels) {
-          edit_fun(&user->fine_grained_access_handler().label_permissions(), label);
+          edit_fine_grained_permissions_fun(user->fine_grained_access_handler().label_permissions(), label);
         }
         for (const auto &edgeType : edgeTypes) {
-          edit_fun(&role->fine_grained_access_handler().edge_type_permissions(), edgeType);
+          edit_fine_grained_permissions_fun(role->fine_grained_access_handler().edge_type_permissions(), edgeType);
         }
 
         locked_auth->SaveRole(*role);
diff --git a/tests/e2e/write_procedures/procedures/read.py b/tests/e2e/write_procedures/procedures/read.py
index 3f791a37a..0532fda1a 100644
--- a/tests/e2e/write_procedures/procedures/read.py
+++ b/tests/e2e/write_procedures/procedures/read.py
@@ -13,8 +13,7 @@ import mgp
 
 
 @mgp.read_proc
-def underlying_graph_is_mutable(ctx: mgp.ProcCtx,
-                                object: mgp.Any) -> mgp.Record(mutable=bool):
+def underlying_graph_is_mutable(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record(mutable=bool):
     return mgp.Record(mutable=object.underlying_graph_is_mutable())
 
 
diff --git a/tests/unit/auth.cpp b/tests/unit/auth.cpp
index d270e6f4a..49cb72d1a 100644
--- a/tests/unit/auth.cpp
+++ b/tests/unit/auth.cpp
@@ -11,6 +11,7 @@
 
 #include <algorithm>
 #include <iostream>
+#include <optional>
 
 #include <gflags/gflags.h>
 #include <gtest/gtest.h>
@@ -176,25 +177,29 @@ TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
             user->GetFineGrainedAccessEdgeTypePermissions());
 
   // Grant one label to user .
-  user->fine_grained_access_handler().label_permissions().Grant("labelTest");
+  user->fine_grained_access_handler().label_permissions().Grant("labelTest", LabelPermission::CREATE_DELETE);
   // Grant one edge type to user .
-  user->fine_grained_access_handler().edge_type_permissions().Grant("edgeTypeTest");
+  user->fine_grained_access_handler().edge_type_permissions().Grant("edgeTypeTest", LabelPermission::CREATE_DELETE);
 
   // Check permissions.
-  ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest"), PermissionLevel::GRANT);
-  ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest"), PermissionLevel::GRANT);
+  ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest", LabelPermission::READ),
+            PermissionLevel::GRANT);
+  ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest", LabelPermission::READ),
+            PermissionLevel::GRANT);
   ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
   ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
             user->GetFineGrainedAccessEdgeTypePermissions());
 
   // Deny one label to user .
-  user->fine_grained_access_handler().label_permissions().Deny("labelTest1");
+  user->fine_grained_access_handler().label_permissions().Deny("labelTest1", LabelPermission::READ);
   // Deny one edge type to user .
-  user->fine_grained_access_handler().edge_type_permissions().Deny("edgeTypeTest1");
+  user->fine_grained_access_handler().edge_type_permissions().Deny("edgeTypeTest1", LabelPermission::READ);
 
   // Check permissions.
-  ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest1"), PermissionLevel::DENY);
-  ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest1"), PermissionLevel::DENY);
+  ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest1", LabelPermission::READ),
+            PermissionLevel::DENY);
+  ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest1", LabelPermission::READ),
+            PermissionLevel::DENY);
   ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
   ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
             user->GetFineGrainedAccessEdgeTypePermissions());
@@ -205,25 +210,29 @@ TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
   ASSERT_NE(role, std::nullopt);
 
   // Grant label and edge type to role and role to user.
-  role->fine_grained_access_handler().label_permissions().Grant("roleLabelTest");
-  role->fine_grained_access_handler().edge_type_permissions().Grant("roleEdgeTypeTest");
+  role->fine_grained_access_handler().label_permissions().Grant("roleLabelTest", LabelPermission::CREATE_DELETE);
+  role->fine_grained_access_handler().edge_type_permissions().Grant("roleEdgeTypeTest", LabelPermission::CREATE_DELETE);
   user->SetRole(*role);
 
   // Check permissions.
   {
-    ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest"), PermissionLevel::GRANT);
-    ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest"), PermissionLevel::GRANT);
+    ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest", LabelPermission::READ),
+              PermissionLevel::GRANT);
+    ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest", LabelPermission::READ),
+              PermissionLevel::GRANT);
   }
 
   // Deny label and edge type to role and role to user.
-  role->fine_grained_access_handler().label_permissions().Deny("roleLabelTest1");
-  role->fine_grained_access_handler().edge_type_permissions().Deny("roleEdgeTypeTest1");
+  role->fine_grained_access_handler().label_permissions().Deny("roleLabelTest1", LabelPermission::READ);
+  role->fine_grained_access_handler().edge_type_permissions().Deny("roleEdgeTypeTest1", LabelPermission::READ);
   user->SetRole(*role);
 
   // Check permissions.
   {
-    ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest1"), PermissionLevel::DENY);
-    ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest1"), PermissionLevel::DENY);
+    ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest1", LabelPermission::READ),
+              PermissionLevel::DENY);
+    ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest1", LabelPermission::READ),
+              PermissionLevel::DENY);
   }
 }
 
@@ -475,6 +484,361 @@ TEST(AuthWithoutStorage, PermissionsMaskTest) {
   ASSERT_EQ(p4.denies(), 2);
 }
 
+TEST(AuthWithoutStorage, FineGrainedAccessPermissions) {
+  const std::string any_label = "AnyString";
+  const std::string check_label = "Label";
+  const std::string non_check_label = "OtherLabel";
+  const std::string asterisk = "*";
+
+  {
+    FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
+    ASSERT_TRUE(fga_permissions1 == fga_permissions2);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::DENY);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(any_label, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_FALSE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(any_label, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_FALSE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(any_label, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), LabelPermissionAll);
+    ASSERT_FALSE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(any_label);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), LabelPermissionAll);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(any_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(any_label);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(any_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(asterisk);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(any_label);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), LabelPermission::EDIT | LabelPermission::READ);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(any_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(any_label);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(any_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(asterisk);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(check_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(non_check_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(asterisk);
+
+    ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
+    ASSERT_TRUE(fga_permissions.GetPermissions().empty());
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::EDIT);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(asterisk, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(asterisk, LabelPermission::EDIT);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Deny(asterisk, LabelPermission::READ);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::DENY);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::READ);
+    fga_permissions.Grant(check_label, LabelPermission::EDIT);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::READ);
+    fga_permissions.Deny(check_label, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(check_label, LabelPermission::EDIT);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(check_label, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(asterisk, LabelPermission::CREATE_DELETE);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(asterisk, LabelPermission::EDIT);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(asterisk, LabelPermission::READ);
+
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(any_label, LabelPermission::READ), PermissionLevel::DENY);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(check_label, LabelPermission::READ);
+    fga_permissions.Revoke(asterisk);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::DENY);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(check_label, LabelPermission::EDIT);
+    fga_permissions.Revoke(asterisk);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::DENY);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions;
+    fga_permissions.Grant(asterisk, LabelPermission::CREATE_DELETE);
+    fga_permissions.Deny(check_label, LabelPermission::CREATE_DELETE);
+    fga_permissions.Revoke(asterisk);
+
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(check_label, LabelPermission::READ), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions.Has(non_check_label, LabelPermission::READ), PermissionLevel::DENY);
+  }
+}
+TEST_F(AuthWithStorage, FineGrainedAccessCheckerMerge) {
+  auto any_label = "AnyString";
+  auto check_label = "Label";
+  auto asterisk = "*";
+
+  {
+    FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
+    fga_permissions1.Grant(asterisk, LabelPermission::READ);
+
+    auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
+
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
+    fga_permissions2.Grant(asterisk, LabelPermission::READ);
+
+    auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
+
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
+    fga_permissions1.Grant(asterisk, LabelPermission::READ);
+    fga_permissions2.Grant(asterisk, LabelPermission::EDIT);
+
+    auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
+
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions3.Has(any_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
+    fga_permissions1.Grant(asterisk, LabelPermission::READ);
+    fga_permissions1.Grant(check_label, LabelPermission::EDIT);
+    fga_permissions2.Grant(asterisk, LabelPermission::EDIT);
+
+    auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
+
+    ASSERT_EQ(fga_permissions3.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(check_label, LabelPermission::EDIT), PermissionLevel::GRANT);
+    ASSERT_EQ(fga_permissions3.Has(check_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+
+  {
+    FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
+    fga_permissions1.Grant(asterisk, LabelPermission::READ);
+    fga_permissions1.Grant(check_label, LabelPermission::CREATE_DELETE);
+    fga_permissions2.Grant(asterisk, LabelPermission::EDIT);
+    fga_permissions2.Grant(check_label, LabelPermission::READ);
+
+    auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
+
+    ASSERT_EQ(fga_permissions3.Has(check_label, LabelPermission::CREATE_DELETE), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(check_label, LabelPermission::EDIT), PermissionLevel::DENY);
+    ASSERT_EQ(fga_permissions3.Has(check_label, LabelPermission::READ), PermissionLevel::GRANT);
+  }
+}
+
 TEST(AuthWithoutStorage, UserSerializeDeserialize) {
   auto user = User("test");
   user.permissions().Grant(Permission::MATCH);
diff --git a/tests/unit/auth_checker.cpp b/tests/unit/auth_checker.cpp
index 54f796b96..307e5742e 100644
--- a/tests/unit/auth_checker.cpp
+++ b/tests/unit/auth_checker.cpp
@@ -46,7 +46,7 @@ class FineGrainedAuthCheckerFixture : public testing::Test {
 
 TEST_F(FineGrainedAuthCheckerFixture, GrantedAllLabels) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().label_permissions().Grant("*");
+  user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::LabelPermission::CREATE_DELETE);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
@@ -59,7 +59,7 @@ TEST_F(FineGrainedAuthCheckerFixture, GrantedAllLabels) {
 
 TEST_F(FineGrainedAuthCheckerFixture, GrantedAllEdgeTypes) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().edge_type_permissions().Grant("*");
+  user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::LabelPermission::CREATE_DELETE);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_TRUE(auth_checker.Accept(dba, r1));
@@ -70,7 +70,7 @@ TEST_F(FineGrainedAuthCheckerFixture, GrantedAllEdgeTypes) {
 
 TEST_F(FineGrainedAuthCheckerFixture, DeniedAllLabels) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().label_permissions().Deny("*");
+  user.fine_grained_access_handler().label_permissions().Deny("*", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_FALSE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
@@ -83,7 +83,7 @@ TEST_F(FineGrainedAuthCheckerFixture, DeniedAllLabels) {
 
 TEST_F(FineGrainedAuthCheckerFixture, DeniedAllEdgeTypes) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().edge_type_permissions().Deny("*");
+  user.fine_grained_access_handler().edge_type_permissions().Deny("*", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_FALSE(auth_checker.Accept(dba, r1));
@@ -94,7 +94,7 @@ TEST_F(FineGrainedAuthCheckerFixture, DeniedAllEdgeTypes) {
 
 TEST_F(FineGrainedAuthCheckerFixture, GrantLabel) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().label_permissions().Grant("l1");
+  user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::LabelPermission::CREATE_DELETE);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
@@ -103,7 +103,7 @@ TEST_F(FineGrainedAuthCheckerFixture, GrantLabel) {
 
 TEST_F(FineGrainedAuthCheckerFixture, DenyLabel) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().label_permissions().Deny("l3");
+  user.fine_grained_access_handler().label_permissions().Deny("l3", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::NEW));
@@ -112,9 +112,9 @@ TEST_F(FineGrainedAuthCheckerFixture, DenyLabel) {
 
 TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificLabels) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().label_permissions().Grant("l1");
-  user.fine_grained_access_handler().label_permissions().Grant("l2");
-  user.fine_grained_access_handler().label_permissions().Deny("l3");
+  user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::LabelPermission::CREATE_DELETE);
+  user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::LabelPermission::CREATE_DELETE);
+  user.fine_grained_access_handler().label_permissions().Deny("l3", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
@@ -127,9 +127,9 @@ TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificLabels) {
 
 TEST_F(FineGrainedAuthCheckerFixture, MultipleVertexLabels) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().label_permissions().Grant("l1");
-  user.fine_grained_access_handler().label_permissions().Grant("l2");
-  user.fine_grained_access_handler().label_permissions().Deny("l3");
+  user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::LabelPermission::CREATE_DELETE);
+  user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::LabelPermission::CREATE_DELETE);
+  user.fine_grained_access_handler().label_permissions().Deny("l3", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
   ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l3")).HasValue());
   ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l1")).HasValue());
@@ -143,7 +143,8 @@ TEST_F(FineGrainedAuthCheckerFixture, MultipleVertexLabels) {
 
 TEST_F(FineGrainedAuthCheckerFixture, GrantEdgeType) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1");
+  user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1",
+                                                                   memgraph::auth::LabelPermission::CREATE_DELETE);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_TRUE(auth_checker.Accept(dba, r1));
@@ -151,7 +152,7 @@ TEST_F(FineGrainedAuthCheckerFixture, GrantEdgeType) {
 
 TEST_F(FineGrainedAuthCheckerFixture, DenyEdgeType) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_1");
+  user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_1", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_FALSE(auth_checker.Accept(dba, r1));
@@ -159,8 +160,9 @@ TEST_F(FineGrainedAuthCheckerFixture, DenyEdgeType) {
 
 TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificEdgeTypes) {
   memgraph::auth::User user{"test"};
-  user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1");
-  user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_2");
+  user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1",
+                                                                   memgraph::auth::LabelPermission::CREATE_DELETE);
+  user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_2", memgraph::auth::LabelPermission::READ);
   memgraph::glue::FineGrainedAuthChecker auth_checker{user};
 
   ASSERT_TRUE(auth_checker.Accept(dba, r1));
diff --git a/tests/unit/query_plan_match_filter_return.cpp b/tests/unit/query_plan_match_filter_return.cpp
index fb22b6cc0..3d6256db5 100644
--- a/tests/unit/query_plan_match_filter_return.cpp
+++ b/tests/unit/query_plan_match_filter_return.cpp
@@ -427,9 +427,11 @@ TEST_F(ExpandFixture, ExpandWithEdgeFiltering) {
 
   auto user = memgraph::auth::User("test");
 
-  user.fine_grained_access_handler().edge_type_permissions().Grant("Edge");
-  user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_test");
-  user.fine_grained_access_handler().label_permissions().Grant("*");
+  user.fine_grained_access_handler().edge_type_permissions().Grant("Edge",
+                                                                   memgraph::auth::LabelPermission::CREATE_DELETE);
+  user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_test",
+                                                                  memgraph::auth::LabelPermission::READ);
+  user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::LabelPermission::CREATE_DELETE);
   memgraph::storage::EdgeTypeId edge_type_test{db.NameToEdgeType("edge_type_test")};
 
   ASSERT_TRUE(dba.InsertEdge(&v1, &v2, edge_type_test).HasValue());
@@ -448,7 +450,8 @@ TEST_F(ExpandFixture, ExpandWithEdgeFiltering) {
   EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD));
   EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD));
 
-  user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_test");
+  user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_test",
+                                                                   memgraph::auth::LabelPermission::CREATE_DELETE);
 
   EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD));
   EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD));