Merge pull request #484 from memgraph/E129-MG-label-based-authorization

* Add label-based authorization

Co-authored-by: Boris Taševski <boris.tasevski@memgraph.com>
Co-authored-by: Josip Mrden <josip.mrden@memgraph.com>
Co-authored-by: Niko Krvavica <niko.krvavica@memgraph.com>
Co-authored-by: Bruno Sacaric <bruno.sacaric@memgraph.com>
This commit is contained in:
Marko Budiselić 2022-09-16 15:12:54 +02:00 committed by GitHub
commit 898c894a48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 10431 additions and 1055 deletions

View File

@ -130,7 +130,7 @@ jobs:
source /opt/toolchain-v4/activate
# Restrict clang-tidy results only to the modified parts
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build -regex ".+\.cpp" | tee ./build/clang_tidy_output.txt
# Fail if any warning is reported
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null

View File

@ -37,12 +37,19 @@ extern "C" {
/// All functions return an error code that can be used to figure out whether the API call was successful or not. In
/// case of failure, the specific error code can be used to identify the reason of the failure.
MGP_ENUM_CLASS MGP_NODISCARD mgp_error{
MGP_ERROR_NO_ERROR, MGP_ERROR_UNKNOWN_ERROR,
MGP_ERROR_UNABLE_TO_ALLOCATE, MGP_ERROR_INSUFFICIENT_BUFFER,
MGP_ERROR_OUT_OF_RANGE, MGP_ERROR_LOGIC_ERROR,
MGP_ERROR_DELETED_OBJECT, MGP_ERROR_INVALID_ARGUMENT,
MGP_ERROR_KEY_ALREADY_EXISTS, MGP_ERROR_IMMUTABLE_OBJECT,
MGP_ERROR_VALUE_CONVERSION, MGP_ERROR_SERIALIZATION_ERROR,
MGP_ERROR_NO_ERROR,
MGP_ERROR_UNKNOWN_ERROR,
MGP_ERROR_UNABLE_TO_ALLOCATE,
MGP_ERROR_INSUFFICIENT_BUFFER,
MGP_ERROR_OUT_OF_RANGE,
MGP_ERROR_LOGIC_ERROR,
MGP_ERROR_DELETED_OBJECT,
MGP_ERROR_INVALID_ARGUMENT,
MGP_ERROR_KEY_ALREADY_EXISTS,
MGP_ERROR_IMMUTABLE_OBJECT,
MGP_ERROR_VALUE_CONVERSION,
MGP_ERROR_SERIALIZATION_ERROR,
MGP_ERROR_AUTHORIZATION_ERROR,
};
///@}

View File

@ -555,7 +555,7 @@ class List {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a List from the copy of the given @ref mgp_list.
@ -653,7 +653,7 @@ class Map {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a Map from the copy of the given @ref mgp_map.
@ -788,7 +788,7 @@ class Node {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
/// @brief Creates a Node from the copy of the given @ref mgp_vertex.
explicit Node(mgp_vertex *ptr) : ptr_(mgp::vertex_copy(ptr, memory)) {}
@ -846,7 +846,7 @@ class Relationship {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a Relationship from the copy of the given @ref mgp_edge.
@ -897,7 +897,7 @@ class Path {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a Path from the copy of the given @ref mgp_path.
@ -949,7 +949,7 @@ class Date {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a Date object from the copy of the given @ref mgp_date.
@ -1003,7 +1003,7 @@ class LocalTime {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a LocalTime object from the copy of the given @ref mgp_local_time.
@ -1063,7 +1063,7 @@ class LocalDateTime {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a LocalDateTime object from the copy of the given @ref mgp_local_date_time.
@ -1131,7 +1131,7 @@ class Duration {
friend class Value;
friend class Record;
friend class Result;
friend struct Parameter;
friend class Parameter;
public:
/// @brief Creates a Duration from the copy of the given @ref mgp_duration.
@ -2743,7 +2743,7 @@ void AddFunction(mgp_func_cb callback, std::string_view name, std::vector<mgp::P
mgp_module *module, mgp_memory *memory) {
auto func = mgp::module_add_function(module, name.data(), callback);
for (const auto parameter : parameters) {
for (const auto &parameter : parameters) {
auto parameter_name = parameter.name.data();
if (!parameter.optional) {

View File

@ -134,6 +134,15 @@ class SerializationError(_mgp.SerializationError):
pass
class AuthorizationError(_mgp.AuthorizationError):
"""
Signals that the user doesn't have sufficient permissions to perform
procedure call.
"""
pass
class Label:
"""Label of a `Vertex`."""
@ -146,7 +155,7 @@ class Label:
def name(self) -> str:
"""
Get the name of the label.
Returns:
A string that represents the name of the label.
@ -195,20 +204,20 @@ class Properties:
def get(self, property_name: str, default=None) -> object:
"""
Get the value of a property with the given name or return default value.
Args:
Args:
property_name: String that represents property name.
default: Default value return if there is no property.
Returns:
Any object value that property under `property_name` has or default value otherwise.
Raises:
InvalidContextError: If `edge` or `vertex` is out of context.
Returns:
Any object value that property under `property_name` has or default value otherwise.
Raises:
InvalidContextError: If `edge` or `vertex` is out of context.
UnableToAllocateError: If unable to allocate a `mgp.Value`.
DeletedObjectError: If the `object` has been deleted.
Examples:
Examples:
```
vertex.properties.get(property_name)
edge.properties.get(property_name)
@ -227,23 +236,23 @@ class Properties:
Set the value of the property. When the value is `None`, then the
property is removed.
Args:
property_name: String that represents property name.
Args:
property_name: String that represents property name.
value: Object that represents value to be set.
Raises:
Raises:
UnableToAllocateError: If unable to allocate memory for storing the property.
ImmutableObjectError: If the object is immutable.
DeletedObjectError: If the object has been deleted.
SerializationError: If the object has been modified by another transaction.
ValueConversionError: If `value` is vertex, edge or path.
Examples:
```
vertex.properties.set(property_name, value)
edge.properties.set(property_name, value)
```
"""
self[property_name] = value
@ -252,15 +261,15 @@ class Properties:
Iterate over the properties. Doesnt return a dynamic view of the properties but copies the
current properties.
Returns:
Returns:
Iterable `Property` of names and values.
Raises:
InvalidContextError: If edge or vertex is out of context.
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
items = vertex.properties.items()
for it in items:
@ -290,15 +299,15 @@ class Properties:
Iterate over property names. Doesnt return a dynamic view of the property names but copies the
name of the current properties.
Returns:
Returns:
Iterable list of strings that represent names/keys of properties.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
graph.vertex.properties.keys()
graph.edge.properties.keys()
@ -314,20 +323,20 @@ class Properties:
Iterate over property values. Doesnt return a dynamic view of the property values but copies the
value of the current properties.
Returns:
Returns:
Iterable list of property values.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
vertex.properties.values()
edge.properties.values()
```
"""
if not self._vertex_or_edge.is_valid():
raise InvalidContextError()
@ -338,15 +347,15 @@ class Properties:
"""
Get the number of properties.
Returns:
Returns:
A number of properties on vertex or edge.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
len(vertex.properties)
len(edge.properties)
@ -363,15 +372,15 @@ class Properties:
"""
Iterate over property names.
Returns:
Returns:
Iterable list of strings that represent names of properties.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
iter(vertex.properties)
iter(edge.properties)
@ -386,24 +395,24 @@ class Properties:
def __getitem__(self, property_name: str) -> object:
"""
Get the value of a property with the given name or raise KeyError.
Args:
Args:
property_name: String that represents property name.
Returns:
Returns:
Any value that property under property_name have.
Raises:
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate a mgp.Value.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
vertex.properties[property_name]
edge.properties[property_name]
```
"""
if not self._vertex_or_edge.is_valid():
raise InvalidContextError()
@ -417,18 +426,18 @@ class Properties:
Set the value of the property. When the value is `None`, then the
property is removed.
Args:
Args:
property_name: String that represents property name.
value: Object that represents value to be set.
Raises:
UnableToAllocateError: If unable to allocate memory for storing the property.
ImmutableObjectError: If the object is immutable.
DeletedObjectError: If the object has been deleted.
SerializationError: If the object has been modified by another transaction.
ValueConversionError: If `value` is vertex, edge or path.
Examples:
Examples:
```
vertex.properties[property_name] = value
edge.properties[property_name] = value
@ -443,18 +452,18 @@ class Properties:
"""
Check if there is a property with the given name.
Args:
Args:
property_name: String that represents property name
Returns:
Bool value that depends if there is with a given name.
Raises:
Bool value that depends if there is with a given name.
Raises:
InvalidContextError: If edge or vertex is out of context.
UnableToAllocateError: If unable to allocate a mgp.Value.
DeletedObjectError: If the object has been deleted.
Examples:
Examples:
```
if property_name in vertex.properties:
```
@ -483,7 +492,7 @@ class EdgeType:
def name(self) -> str:
"""
Get the name of EdgeType.
Returns:
A string that represents the name of EdgeType.
@ -512,7 +521,7 @@ class Edge:
Access to an Edge is only valid during a single execution of a procedure in
a query. You should not globally store an instance of an Edge. Using an
invalid Edge instance will raise InvalidContextError.
"""
__slots__ = ("_edge",)
@ -532,10 +541,10 @@ class Edge:
def is_valid(self) -> bool:
"""
Check if `edge` is in a valid context and may be used.
Returns:
A `bool` value depends on if the `edge` is in a valid context.
A `bool` value depends on if the `edge` is in a valid context.
Examples:
```edge.is_valid()```
@ -543,15 +552,15 @@ class Edge:
return self._edge.is_valid()
def underlying_graph_is_mutable(self) -> bool:
"""
Check if the `graph` can be modified.
"""
Check if the `graph` can be modified.
Returns:
Returns:
A `bool` value depends on if the `graph` is mutable.
Examples:
Examples:
```edge.underlying_graph_is_mutable()```
"""
if not self.is_valid():
raise InvalidContextError()
@ -564,10 +573,10 @@ class Edge:
Returns:
`EdgeId` represents ID of the edge.
Raises:
Raises:
InvalidContextError: If edge is out of context.
Examples:
```edge.id```
"""
@ -581,12 +590,12 @@ class Edge:
Get the type of edge.
Returns:
`EdgeType` describing the type of edge.
`EdgeType` describing the type of edge.
Raises:
InvalidContextError: If edge is out of context.
Examples:
Examples:
```edge.type```
"""
if not self.is_valid():
@ -598,10 +607,10 @@ class Edge:
"""
Get the source vertex.
Returns:
Returns:
`Vertex` from where the edge is directed.
Raises:
Raises:
InvalidContextError: If edge is out of context.
Examples:
@ -615,14 +624,14 @@ class Edge:
def to_vertex(self) -> "Vertex":
"""
Get the destination vertex.
Returns:
Returns:
`Vertex` to where the edge is directed.
Raises:
Raises:
InvalidContextError: If edge is out of context.
Examples:
Examples:
```edge.to_vertex```
"""
if not self.is_valid():
@ -635,7 +644,7 @@ class Edge:
Get the properties of the edge.
Returns:
All `Properties` of edge.
All `Properties` of edge.
Raises:
InvalidContextError: If edge is out of context.
@ -692,9 +701,9 @@ class Vertex:
Checks if `Vertex` is in valid context and may be used.
Returns:
A `bool` value depends on if the `Vertex` is in a valid context.
Examples:
A `bool` value depends on if the `Vertex` is in a valid context.
Examples:
```vertex.is_valid()```
"""
@ -702,14 +711,14 @@ class Vertex:
def underlying_graph_is_mutable(self) -> bool:
"""
Check if the `graph` is mutable.
Check if the `graph` is mutable.
Returns:
Returns:
A `bool` value depends on if the `graph` is mutable.
Examples:
Examples:
```vertex.underlying_graph_is_mutable()```
"""
if not self.is_valid():
raise InvalidContextError()
@ -722,10 +731,10 @@ class Vertex:
Returns:
`VertexId` represents ID of the vertex.
Raises:
Raises:
InvalidContextError: If vertex is out of context.
Examples:
```vertex.id```
"""
@ -738,15 +747,15 @@ class Vertex:
"""
Get the labels of the vertex.
Returns:
Returns:
A tuple of `Label` representing vertex Labels
Raises:
Raises:
InvalidContextError: If vertex is out of context.
OutOfRangeError: If some of the labels are removed while collecting the labels.
DeletedObjectError: If `Vertex` has been deleted.
Examples:
Examples:
```vertex.labels```
"""
if not self.is_valid():
@ -757,17 +766,17 @@ class Vertex:
"""
Add the label to the vertex.
Args:
label: String label to be added.
Raises:
Args:
label: String label to be added.
Raises:
InvalidContextError: If `Vertex` is out of context.
UnableToAllocateError: If unable to allocate memory for storing the label.
ImmutableObjectError: If `Vertex` is immutable.
DeletedObjectError: If `Vertex` has been deleted.
SerializationError: If `Vertex` has been modified by another transaction.
Examples:
Examples:
```vertex.add_label(label)```
"""
if not self.is_valid():
@ -778,15 +787,15 @@ class Vertex:
"""
Remove the label from the vertex.
Args:
label: String label to be deleted
Raises:
Args:
label: String label to be deleted
Raises:
InvalidContextError: If `Vertex` is out of context.
ImmutableObjectError: If `Vertex` is immutable.
DeletedObjectError: If `Vertex` has been deleted.
SerializationError: If `Vertex` has been modified by another transaction.
Examples:
Examples:
```vertex.remove_label(label)```
"""
if not self.is_valid():
@ -798,13 +807,13 @@ class Vertex:
"""
Get the properties of the vertex.
Returns:
Returns:
`Properties` on a current vertex.
Raises:
Raises:
InvalidContextError: If `Vertex` is out of context.
Examples:
Examples:
```vertex.properties```
"""
if not self.is_valid():
@ -820,13 +829,13 @@ class Vertex:
Returns:
Iterable list of `Edge` objects that are directed in towards the current vertex.
Raises:
Raises:
InvalidContextError: If `Vertex` is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If `Vertex` has been deleted.
Examples:
Examples:
```for edge in vertex.in_edges:```
"""
if not self.is_valid():
@ -850,12 +859,12 @@ class Vertex:
Returns:
Iterable list of `Edge` objects that are directed out of the current vertex.
Raises:
Raises:
InvalidContextError: If `Vertex` is out of context.
UnableToAllocateError: If unable to allocate an iterator.
DeletedObjectError: If `Vertex` has been deleted.
Examples:
Examples:
```for edge in vertex.out_edges:```
"""
if not self.is_valid():
@ -888,7 +897,7 @@ class Path:
def __init__(self, starting_vertex_or_path: typing.Union[_mgp.Path, Vertex]):
"""Initialize with a starting Vertex.
Raises:
Raises:
InvalidContextError: If passed in Vertex is invalid.
UnableToAllocateError: If cannot allocate a path.
"""
@ -932,10 +941,10 @@ class Path:
def is_valid(self) -> bool:
"""
Check if `Path` is in valid context and may be used.
Returns:
A `bool` value depends on if the `Path` is in a valid context.
A `bool` value depends on if the `Path` is in a valid context.
Examples:
```path.is_valid()```
"""
@ -948,15 +957,15 @@ class Path:
The last vertex on the path will become the other endpoint of the given
edge, as continued from the current last vertex.
Args:
Args:
edge: `Edge` that is added to the path
Raises:
Raises:
InvalidContextError: If using an invalid `Path` instance or if passed in `Edge` is invalid.
LogicErrorError: If the current last vertex in the path is not part of the given edge.
UnableToAllocateError: If unable to allocate memory for path extension.
Examples:
Examples:
```path.expand(edge)```
"""
if not isinstance(edge, Edge):
@ -973,14 +982,14 @@ class Path:
"""
Vertices are ordered from the start to the end of the path.
Returns:
A tuple of `Vertex` objects order from start to end of the path.
Returns:
A tuple of `Vertex` objects order from start to end of the path.
Raises:
InvalidContextError: If using an invalid Path instance.
Examples:
```path.vertices```
```path.vertices```
"""
if not self.is_valid():
raise InvalidContextError()
@ -994,11 +1003,11 @@ class Path:
"""
Edges are ordered from the start to the end of the path.
Returns:
Returns:
A tuple of `Edge` objects order from start to end of the path
Raises:
Raises:
InvalidContextError: If using an invalid `Path` instance.
Examples:
Examples:
```path.edges```
"""
if not self.is_valid():
@ -1039,10 +1048,10 @@ class Vertices:
def is_valid(self) -> bool:
"""
Check if `Vertices` is in valid context and may be used.
Returns:
A `bool` value depends on if the `Vertices` is in valid context.
A `bool` value depends on if the `Vertices` is in valid context.
Examples:
```vertices.is_valid()```
"""
@ -1052,14 +1061,14 @@ class Vertices:
"""
Iterate over vertices.
Returns:
Iterable list of `Vertex` objects.
Returns:
Iterable list of `Vertex` objects.
Raises:
Raises:
InvalidContextError: If context is invalid.
UnableToAllocateError: If unable to allocate an iterator or a vertex.
Examples:
Examples:
```
for vertex in graph.vertices:
```
@ -1080,18 +1089,18 @@ class Vertices:
def __contains__(self, vertex):
"""
Check if Vertices contain the given vertex.
Check if Vertices contain the given vertex.
Args:
Args:
vertex: `Vertex` to be checked if it is a part of graph `Vertices`.
Returns:
Bool value depends if there is `Vertex` in graph `Vertices`.
Bool value depends if there is `Vertex` in graph `Vertices`.
Raises:
UnableToAllocateError: If unable to allocate the vertex.
Examples:
Examples:
```if vertex in graph.vertices:```
"""
try:
@ -1104,14 +1113,14 @@ class Vertices:
"""
Get the number of vertices.
Returns:
Returns:
A number of vertices in the graph.
Raises:
Raises:
InvalidContextError: If context is invalid.
UnableToAllocateError: If unable to allocate an iterator or a vertex.
Examples:
Examples:
```len(graph.vertices)```
"""
if not self._len:
@ -1140,9 +1149,9 @@ class Graph:
Check if `graph` is in a valid context and may be used.
Returns:
A `bool` value depends on if the `graph` is in a valid context.
Examples:
A `bool` value depends on if the `graph` is in a valid context.
Examples:
```graph.is_valid()```
"""
@ -1169,7 +1178,7 @@ class Graph:
Examples:
```graph.get_vertex_by_id(vertex_id)```
"""
if not self.is_valid():
raise InvalidContextError()
@ -1207,11 +1216,11 @@ class Graph:
def is_mutable(self) -> bool:
"""
Check if the graph is mutable. Thus it can be used to modify vertices and edges.
Returns:
A `bool` value that depends if the graph is mutable or not.
Examples:
Returns:
A `bool` value that depends if the graph is mutable or not.
Examples:
```graph.is_mutable()```
"""
if not self.is_valid():
@ -1222,14 +1231,14 @@ class Graph:
"""
Create an empty vertex.
Returns:
Created `Vertex`.
Returns:
Created `Vertex`.
Raises:
Raises:
ImmutableObjectError: If `graph` is immutable.
UnableToAllocateError: If unable to allocate a vertex.
Examples:
Examples:
Creating an empty vertex.
```vertex = graph.create_vertex()```
@ -1249,7 +1258,7 @@ class Graph:
LogicErrorError: If `vertex` has edges.
SerializationError: If `vertex` has been modified by
another transaction.
Examples:
Examples:
```graph.delete_vertex(vertex)```
"""
@ -1260,14 +1269,14 @@ class Graph:
def detach_delete_vertex(self, vertex: Vertex) -> None:
"""
Delete a vertex and all of its edges.
Args:
Args:
vertex: `Vertex` to be deleted with all of its edges
Raises:
Raises:
ImmutableObjectError: If `graph` is immutable.
SerializationError: If `vertex` has been modified by another transaction.
Examples:
Examples:
```graph.detach_delete_vertex(vertex)```
"""
if not self.is_valid():
@ -1277,18 +1286,18 @@ class Graph:
def create_edge(self, from_vertex: Vertex, to_vertex: Vertex, edge_type: EdgeType) -> None:
"""
Create an edge.
Args:
from_vertex: `Vertex` from where edge is directed.
to_vertex: `Vertex' to where edge is directed.
edge_type: `EdgeType` defines the type of edge.
Args:
from_vertex: `Vertex` from where edge is directed.
to_vertex: `Vertex' to where edge is directed.
edge_type: `EdgeType` defines the type of edge.
Raises:
ImmutableObjectError: If `graph` is immutable.
UnableToAllocateError: If unable to allocate an edge.
DeletedObjectError: If `from_vertex` or `to_vertex` has been deleted.
SerializationError: If `from_vertex` or `to_vertex` has been modified by another transaction.
Examples:
Examples:
```graph.create_edge(from_vertex, vertex, edge_type)```
"""
if not self.is_valid():
@ -1301,8 +1310,8 @@ class Graph:
Args:
edge: `Edge` to be deleted
Raises:
Raises:
ImmutableObjectError if `graph` is immutable.
Raise SerializationError if `edge`, its source or destination vertex has been modified by another transaction.
"""
@ -1337,15 +1346,15 @@ class ProcCtx:
@property
def graph(self) -> Graph:
"""
Access to `Graph` object.
Returns:
Graph object.
Access to `Graph` object.
Raises:
Returns:
Graph object.
Raises:
InvalidContextError: If context is invalid.
Examples:
Examples:
```context.graph```
"""
if not self.is_valid():
@ -1969,6 +1978,8 @@ def _wrap_exceptions():
raise ValueConversionError(e)
except _mgp.SerializationError as e:
raise SerializationError(e)
except _mgp.AuthorizationError as e:
raise AuthorizationError(e)
return wrapped_func

View File

@ -173,6 +173,10 @@ class SerializationError(Exception):
pass
class AuthorizationError(Exception):
pass
def type_nullable(elem: Any):
pass

View File

@ -12,6 +12,7 @@ add_subdirectory(memory)
add_subdirectory(storage/v2)
add_subdirectory(integrations)
add_subdirectory(query)
add_subdirectory(glue)
add_subdirectory(slk)
add_subdirectory(rpc)
add_subdirectory(auth)
@ -31,13 +32,11 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
# Memgraph Single Node v2 Executable
# ----------------------------------------------------------------------------
set(mg_single_node_v2_sources
glue/communication.cpp
memgraph.cpp
glue/auth.cpp
memgraph.cpp
)
set(mg_single_node_v2_libs stdc++fs Threads::Threads
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings)
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-glue)
if (MG_ENTERPRISE)
# These are enterprise subsystems
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)

View File

@ -226,7 +226,7 @@ std::vector<auth::User> Auth::AllUsers() const {
if (username != utils::ToLowerCase(username)) continue;
auto user = GetUser(username);
if (user) {
ret.push_back(*user);
ret.push_back(std::move(*user));
}
}
return ret;
@ -306,7 +306,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
if (it->second == rolename) {
auto user = GetUser(username);
if (user) {
ret.push_back(*user);
ret.push_back(std::move(*user));
} else {
throw AuthException("Couldn't load user '{}'!", username);
}

View File

@ -21,7 +21,7 @@
namespace memgraph::auth {
/**
* This class serves as the main Authentication/Authorization storage.
* It provides functions for managing Users, Roles and Permissions.
* It provides functions for managing Users, Roles, Permissions and FineGrainedAccessPermissions.
* NOTE: The non-const functions in this class aren't thread safe.
* TODO (mferencevic): Disable user/role modification functions when they are
* being managed by the auth module.

View File

@ -8,6 +8,7 @@
#include "auth/models.hpp"
#include <cstdint>
#include <regex>
#include <gflags/gflags.h>
@ -16,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"
@ -30,6 +32,7 @@ DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
namespace memgraph::auth {
namespace {
// Constant list of all available permissions.
const std::vector<Permission> kPermissionsAll = {
Permission::MATCH, Permission::CREATE, Permission::MERGE, Permission::DELETE,
@ -98,6 +101,55 @@ std::string PermissionLevelToString(PermissionLevel level) {
}
}
#ifdef MG_ENTERPRISE
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::NOTHING;
}
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::NOTHING:
return "NOTHING";
}
}
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();
} else if (first.GetGlobalPermission().has_value()) {
global_permission = *first.GetGlobalPermission();
}
for (const auto &[label_name, permission] : second.GetPermissions()) {
permissions[label_name] = permission;
}
return FineGrainedAccessPermissions(permissions, global_permission);
}
#endif
Permissions::Permissions(uint64_t grants, uint64_t denies) {
// The deny bitmask has higher priority than the grant bitmask.
denies_ = denies;
@ -171,7 +223,7 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
throw AuthException("Couldn't load permissions data!");
}
return {data["grants"], data["denies"]};
return Permissions{data["grants"], data["denies"]};
}
uint64_t Permissions::grants() const { return grants_; }
@ -183,19 +235,191 @@ bool operator==(const Permissions &first, const Permissions &second) {
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_map<std::string, uint64_t> &permissions,
const std::optional<uint64_t> &global_permission)
: permissions_(permissions), global_permission_(global_permission) {}
PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission,
const FineGrainedPermission fine_grained_permission) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return PermissionLevel::GRANT;
}
const auto concrete_permission = std::invoke([&]() -> uint64_t {
if (permissions_.contains(permission)) {
return permissions_.at(permission);
}
if (global_permission_.has_value()) {
return global_permission_.value();
}
return 0;
});
const auto temp_permission = concrete_permission & fine_grained_permission;
return temp_permission > 0 ? PermissionLevel::GRANT : PermissionLevel::DENY;
}
void FineGrainedAccessPermissions::Grant(const std::string &permission,
const FineGrainedPermission fine_grained_permission) {
if (permission == kAsterisk) {
global_permission_ = CalculateGrant(fine_grained_permission);
} else {
permissions_[permission] = CalculateGrant(fine_grained_permission);
}
}
void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
if (permission == kAsterisk) {
permissions_.clear();
global_permission_ = std::nullopt;
} else {
permissions_.erase(permission);
}
}
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
nlohmann::json data = nlohmann::json::object();
data["permissions"] = permissions_;
data["global_permission"] = global_permission_.has_value() ? global_permission_.value() : -1;
return data;
}
FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load permissions data!");
}
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessPermissions{};
}
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_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(FineGrainedPermission fine_grained_permission) {
uint64_t shift{1};
uint64_t result{0};
auto uint_fine_grained_permission = static_cast<uint64_t>(fine_grained_permission);
while (uint_fine_grained_permission > 0) {
result |= uint_fine_grained_permission;
uint_fine_grained_permission >>= shift;
}
return result;
}
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return first.GetPermissions() == second.GetPermissions() &&
first.GetGlobalPermission() == second.GetGlobalPermission();
}
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return !(first == second);
}
FineGrainedAccessHandler::FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions,
FineGrainedAccessPermissions edgeTypePermissions)
: label_permissions_(std::move(labelPermissions)), edge_type_permissions_(std::move(edgeTypePermissions)) {}
const FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() const { return label_permissions_; }
FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() { return label_permissions_; }
const FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() const {
return edge_type_permissions_;
}
FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() { return edge_type_permissions_; }
nlohmann::json FineGrainedAccessHandler::Serialize() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
nlohmann::json data = nlohmann::json::object();
data["label_permissions"] = label_permissions_.Serialize();
data["edge_type_permissions"] = edge_type_permissions_.Serialize();
return data;
}
FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load role data!");
}
if (!data["label_permissions"].is_object() || !data["edge_type_permissions"].is_object()) {
throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
}
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessHandler{};
}
auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
auto edge_type_permissions = FineGrainedAccessPermissions::Deserialize(data["edge_type_permissions"]);
return FineGrainedAccessHandler(std::move(label_permissions), std::move(edge_type_permissions));
}
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
return first.label_permissions_ == second.label_permissions_ &&
first.edge_type_permissions_ == second.edge_type_permissions_;
}
bool operator!=(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
return !(first == second);
}
#endif
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
Role::Role(const std::string &rolename, const Permissions &permissions)
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
#ifdef MG_ENTERPRISE
Role::Role(const std::string &rolename, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler)
: rolename_(utils::ToLowerCase(rolename)),
permissions_(permissions),
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
#endif
const std::string &Role::rolename() const { return rolename_; }
const Permissions &Role::permissions() const { return permissions_; }
Permissions &Role::permissions() { return permissions_; }
#ifdef MG_ENTERPRISE
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();
}
#endif
nlohmann::json Role::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["rolename"] = rolename_;
data["permissions"] = permissions_.Serialize();
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
} else {
data["fine_grained_access_handler"] = {};
}
#endif
return data;
}
@ -207,18 +431,43 @@ Role Role::Deserialize(const nlohmann::json &data) {
throw AuthException("Couldn't load role data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
if (!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load user data!");
}
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
}
#endif
return {data["rolename"], permissions};
}
bool operator==(const Role &first, const Role &second) {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
#endif
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User() {}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
#ifdef MG_ENTERPRISE
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler)
: username_(utils::ToLowerCase(username)),
password_hash_(password_hash),
permissions_(permissions),
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
#endif
bool User::CheckPassword(const std::string &password) {
if (password_hash_.empty()) return true;
return VerifyPassword(password, password_hash_);
@ -260,17 +509,47 @@ void User::ClearRole() { role_ = std::nullopt; }
Permissions User::GetPermissions() const {
if (role_) {
return Permissions(permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies());
return Permissions{permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies()};
}
return permissions_;
}
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessPermissions{};
}
if (role_) {
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 (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return FineGrainedAccessPermissions{};
}
if (role_) {
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();
}
#endif
const std::string &User::username() const { return username_; }
const Permissions &User::permissions() const { return permissions_; }
Permissions &User::permissions() { return permissions_; }
#ifdef MG_ENTERPRISE
const FineGrainedAccessHandler &User::fine_grained_access_handler() const { return fine_grained_access_handler_; }
FineGrainedAccessHandler &User::fine_grained_access_handler() { return fine_grained_access_handler_; }
#endif
const Role *User::role() const {
if (role_.has_value()) {
return &role_.value();
@ -283,6 +562,13 @@ nlohmann::json User::Serialize() const {
data["username"] = username_;
data["password_hash"] = password_hash_;
data["permissions"] = permissions_.Serialize();
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
} else {
data["fine_grained_access_handler"] = {};
}
#endif
// The role shouldn't be serialized here, it is stored as a foreign key.
return data;
}
@ -295,11 +581,28 @@ User User::Deserialize(const nlohmann::json &data) {
throw AuthException("Couldn't load user data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
if (!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load user data!");
}
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler};
}
#endif
return {data["username"], data["password_hash"], permissions};
}
bool operator==(const User &first, const User &second) {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
#endif
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_;
}
} // namespace memgraph::auth

View File

@ -10,10 +10,12 @@
#include <optional>
#include <string>
#include <unordered_map>
#include <json/json.hpp>
namespace memgraph::auth {
const std::string kAsterisk = "*";
// These permissions must have values that are applicable for usage in a
// bitmask.
// clang-format off
@ -42,22 +44,61 @@ enum class Permission : uint64_t {
};
// clang-format on
#ifdef MG_ENTERPRISE
// clang-format off
enum class FineGrainedPermission : uint64_t {
NOTHING = 0,
READ = 1,
UPDATE = 1U << 1U,
CREATE_DELETE = 1U << 2U
};
// clang-format on
constexpr inline uint64_t operator|(FineGrainedPermission lhs, FineGrainedPermission rhs) {
return static_cast<uint64_t>(lhs) | static_cast<uint64_t>(rhs);
}
constexpr inline uint64_t operator|(uint64_t lhs, FineGrainedPermission rhs) {
return lhs | static_cast<uint64_t>(rhs);
}
constexpr inline uint64_t operator&(uint64_t lhs, FineGrainedPermission rhs) {
return (lhs & static_cast<uint64_t>(rhs)) != 0;
}
constexpr uint64_t kLabelPermissionAll = memgraph::auth::FineGrainedPermission::CREATE_DELETE |
memgraph::auth::FineGrainedPermission::UPDATE |
memgraph::auth::FineGrainedPermission::READ;
constexpr uint64_t kLabelPermissionMax = static_cast<uint64_t>(memgraph::auth::FineGrainedPermission::CREATE_DELETE);
constexpr uint64_t kLabelPermissionMin = static_cast<uint64_t>(memgraph::auth::FineGrainedPermission::READ);
#endif
// 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);
#ifdef MG_ENTERPRISE
// 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);
#endif
class Permissions final {
public:
Permissions(uint64_t grants = 0, uint64_t denies = 0);
explicit Permissions(uint64_t grants = 0, uint64_t denies = 0);
Permissions(const Permissions &) = default;
Permissions &operator=(const Permissions &) = default;
Permissions(Permissions &&) noexcept = default;
Permissions &operator=(Permissions &&) noexcept = default;
~Permissions() = default;
PermissionLevel Has(Permission permission) const;
@ -88,16 +129,96 @@ bool operator==(const Permissions &first, const Permissions &second);
bool operator!=(const Permissions &first, const Permissions &second);
#ifdef MG_ENTERPRISE
class FineGrainedAccessPermissions final {
public:
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, FineGrainedPermission fine_grained_permission) const;
void Grant(const std::string &permission, FineGrainedPermission fine_grained_permission);
void Revoke(const std::string &permission);
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
const std::unordered_map<std::string, uint64_t> &GetPermissions() const;
const std::optional<uint64_t> &GetGlobalPermission() const;
private:
std::unordered_map<std::string, uint64_t> permissions_{};
std::optional<uint64_t> global_permission_;
static uint64_t CalculateGrant(FineGrainedPermission fine_grained_permission);
};
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
class FineGrainedAccessHandler final {
public:
explicit FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions = FineGrainedAccessPermissions(),
FineGrainedAccessPermissions edgeTypePermissions = FineGrainedAccessPermissions());
FineGrainedAccessHandler(const FineGrainedAccessHandler &) = default;
FineGrainedAccessHandler &operator=(const FineGrainedAccessHandler &) = default;
FineGrainedAccessHandler(FineGrainedAccessHandler &&) noexcept = default;
FineGrainedAccessHandler &operator=(FineGrainedAccessHandler &&) noexcept = default;
~FineGrainedAccessHandler() = default;
const FineGrainedAccessPermissions &label_permissions() const;
FineGrainedAccessPermissions &label_permissions();
const FineGrainedAccessPermissions &edge_type_permissions() const;
FineGrainedAccessPermissions &edge_type_permissions();
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static FineGrainedAccessHandler Deserialize(const nlohmann::json &data);
friend bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
private:
FineGrainedAccessPermissions label_permissions_;
FineGrainedAccessPermissions edge_type_permissions_;
};
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
#endif
class Role final {
public:
Role(const std::string &rolename);
explicit Role(const std::string &rolename);
Role(const std::string &rolename, const Permissions &permissions);
#ifdef MG_ENTERPRISE
Role(const std::string &rolename, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler);
#endif
Role(const Role &) = default;
Role &operator=(const Role &) = default;
Role(Role &&) noexcept = default;
Role &operator=(Role &&) noexcept = default;
~Role() = default;
const std::string &rolename() const;
const Permissions &permissions() const;
Permissions &permissions();
#ifdef MG_ENTERPRISE
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
const FineGrainedAccessPermissions &GetFineGrainedAccessLabelPermissions() const;
const FineGrainedAccessPermissions &GetFineGrainedAccessEdgeTypePermissions() const;
#endif
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
@ -108,6 +229,9 @@ class Role final {
private:
std::string rolename_;
Permissions permissions_;
#ifdef MG_ENTERPRISE
FineGrainedAccessHandler fine_grained_access_handler_;
#endif
};
bool operator==(const Role &first, const Role &second);
@ -115,9 +239,19 @@ bool operator==(const Role &first, const Role &second);
// TODO (mferencevic): Implement password expiry.
class User final {
public:
User(const std::string &username);
User();
explicit User(const std::string &username);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
#ifdef MG_ENTERPRISE
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
FineGrainedAccessHandler fine_grained_access_handler);
#endif
User(const User &) = default;
User &operator=(const User &) = default;
User(User &&) noexcept = default;
User &operator=(User &&) noexcept = default;
~User() = default;
/// @throw AuthException if unable to verify the password.
bool CheckPassword(const std::string &password);
@ -131,6 +265,12 @@ class User final {
Permissions GetPermissions() const;
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
#endif
const std::string &username() const;
const Permissions &permissions() const;
@ -149,8 +289,16 @@ class User final {
std::string username_;
std::string password_hash_;
Permissions permissions_;
#ifdef MG_ENTERPRISE
FineGrainedAccessHandler fine_grained_access_handler_;
#endif
std::optional<Role> role_;
};
bool operator==(const User &first, const User &second);
#ifdef MG_ENTERPRISE
FineGrainedAccessPermissions Merge(const FineGrainedAccessPermissions &first,
const FineGrainedAccessPermissions &second);
#endif
} // namespace memgraph::auth

4
src/glue/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
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)

View File

@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "glue/auth.hpp"
#include "auth/models.hpp"
namespace memgraph::glue {
@ -59,4 +60,20 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
return auth::Permission::WEBSOCKET;
}
}
#ifdef MG_ENTERPRISE
auth::FineGrainedPermission FineGrainedPrivilegeToFineGrainedPermission(
const query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
switch (fine_grained_privilege) {
case query::AuthQuery::FineGrainedPrivilege::NOTHING:
return auth::FineGrainedPermission::NOTHING;
case query::AuthQuery::FineGrainedPrivilege::READ:
return auth::FineGrainedPermission::READ;
case query::AuthQuery::FineGrainedPrivilege::UPDATE:
return auth::FineGrainedPermission::UPDATE;
case query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE:
return auth::FineGrainedPermission::CREATE_DELETE;
}
}
#endif
} // namespace memgraph::glue

View File

@ -20,4 +20,12 @@ namespace memgraph::glue {
*/
auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege);
#ifdef MG_ENTERPRISE
/**
* Converts query::AuthQuery::FineGrainedPrivilege to its corresponding
* auth::EntityPermission.
*/
auth::FineGrainedPermission FineGrainedPrivilegeToFineGrainedPermission(
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege);
#endif
} // namespace memgraph::glue

171
src/glue/auth_checker.cpp Normal file
View File

@ -0,0 +1,171 @@
// 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_checker.hpp"
#include "auth/auth.hpp"
#include "auth/models.hpp"
#include "glue/auth.hpp"
#include "query/frontend/ast/ast.hpp"
#include "utils/license.hpp"
#include "utils/synchronized.hpp"
#ifdef MG_ENTERPRISE
namespace {
bool IsUserAuthorizedLabels(const memgraph::auth::User &user, const memgraph::query::DbAccessor *dba,
const std::vector<memgraph::storage::LabelId> &labels,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return std::all_of(labels.begin(), labels.end(), [dba, &user, fine_grained_privilege](const auto &label) {
return user.GetFineGrainedAccessLabelPermissions().Has(
dba->LabelToName(label), memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(
fine_grained_privilege)) == memgraph::auth::PermissionLevel::GRANT;
});
}
bool IsUserAuthorizedGloballyLabels(const memgraph::auth::User &user,
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return user.GetFineGrainedAccessLabelPermissions().Has(memgraph::auth::kAsterisk, fine_grained_permission) ==
memgraph::auth::PermissionLevel::GRANT;
}
bool IsUserAuthorizedGloballyEdges(const memgraph::auth::User &user,
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return user.GetFineGrainedAccessEdgeTypePermissions().Has(memgraph::auth::kAsterisk, fine_grained_permission) ==
memgraph::auth::PermissionLevel::GRANT;
}
bool IsUserAuthorizedEdgeType(const memgraph::auth::User &user, const memgraph::query::DbAccessor *dba,
const memgraph::storage::EdgeTypeId &edgeType,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return user.GetFineGrainedAccessEdgeTypePermissions().Has(
dba->EdgeTypeToName(edgeType), memgraph::glue::FineGrainedPrivilegeToFineGrainedPermission(
fine_grained_privilege)) == memgraph::auth::PermissionLevel::GRANT;
}
} // namespace
#endif
namespace memgraph::glue {
AuthChecker::AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: auth_(auth) {}
bool AuthChecker::IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const {
std::optional<memgraph::auth::User> maybe_user;
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
#ifdef MG_ENTERPRISE
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> AuthChecker::GetFineGrainedAuthChecker(
const std::string &username, const memgraph::query::DbAccessor *dba) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return {};
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
return std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(*user), dba);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
#endif
bool AuthChecker::IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const auto user_permissions = user.GetPermissions();
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
#ifdef MG_ENTERPRISE
FineGrainedAuthChecker::FineGrainedAuthChecker(auth::User user, const memgraph::query::DbAccessor *dba)
: user_{std::move(user)}, dba_(dba){};
bool FineGrainedAuthChecker::Has(const memgraph::query::VertexAccessor &vertex, const memgraph::storage::View view,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
auto maybe_labels = vertex.Labels(view);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
throw memgraph::query::QueryRuntimeException("Trying to get labels from a deleted node.");
case memgraph::storage::Error::NONEXISTENT_OBJECT:
throw memgraph::query::QueryRuntimeException("Trying to get labels from a node that doesn't exist.");
case memgraph::storage::Error::SERIALIZATION_ERROR:
case memgraph::storage::Error::VERTEX_HAS_EDGES:
case memgraph::storage::Error::PROPERTIES_DISABLED:
throw memgraph::query::QueryRuntimeException("Unexpected error when getting labels.");
}
}
return IsUserAuthorizedLabels(user_, dba_, *maybe_labels, fine_grained_privilege);
}
bool FineGrainedAuthChecker::Has(const memgraph::query::EdgeAccessor &edge,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
return IsUserAuthorizedEdgeType(user_, dba_, edge.EdgeType(), fine_grained_privilege);
}
bool FineGrainedAuthChecker::Has(const std::vector<memgraph::storage::LabelId> &labels,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
return IsUserAuthorizedLabels(user_, dba_, labels, fine_grained_privilege);
}
bool FineGrainedAuthChecker::Has(const memgraph::storage::EdgeTypeId &edge_type,
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
return IsUserAuthorizedEdgeType(user_, dba_, edge_type, fine_grained_privilege);
}
bool FineGrainedAuthChecker::HasGlobalPrivilegeOnVertices(
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return IsUserAuthorizedGloballyLabels(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
}
bool FineGrainedAuthChecker::HasGlobalPrivilegeOnEdges(
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
return true;
}
return IsUserAuthorizedGloballyEdges(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
};
#endif
} // namespace memgraph::glue

67
src/glue/auth_checker.hpp Normal file
View File

@ -0,0 +1,67 @@
// 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 "auth/auth.hpp"
#include "glue/auth.hpp"
#include "query/auth_checker.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
namespace memgraph::glue {
class AuthChecker : public query::AuthChecker {
public:
explicit AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth);
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const override;
#ifdef MG_ENTERPRISE
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string &username, const memgraph::query::DbAccessor *dba) const override;
#endif
[[nodiscard]] static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges);
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};
#ifdef MG_ENTERPRISE
class FineGrainedAuthChecker : public query::FineGrainedAuthChecker {
public:
explicit FineGrainedAuthChecker(auth::User user, const memgraph::query::DbAccessor *dba);
bool Has(const query::VertexAccessor &vertex, memgraph::storage::View view,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool Has(const query::EdgeAccessor &edge,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool Has(const std::vector<memgraph::storage::LabelId> &labels,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool Has(const memgraph::storage::EdgeTypeId &edge_type,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool HasGlobalPrivilegeOnVertices(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
bool HasGlobalPrivilegeOnEdges(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
private:
auth::User user_;
const memgraph::query::DbAccessor *dba_;
};
#endif
} // namespace memgraph::glue

664
src/glue/auth_handler.cpp Normal file
View File

@ -0,0 +1,664 @@
// 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"
#include "utils/license.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<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);
}
#ifdef MG_ENTERPRISE
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;
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
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<std::vector<memgraph::query::TypedValue>> ConstructFineGrainedPrivilegesResult(
const std::vector<FineGrainedPermissionForPrivilegeResult> &privileges) {
std::vector<std::vector<memgraph::query::TypedValue>> grants;
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
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<std::vector<memgraph::query::TypedValue>> ShowFineGrainedUserPrivileges(
const std::optional<memgraph::auth::User> &user) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
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<std::vector<memgraph::query::TypedValue>> ShowFineGrainedRolePrivileges(
const std::optional<memgraph::auth::Role> &role) {
if (!memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
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<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
#ifdef MG_ENTERPRISE
,
{{{memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {memgraph::auth::kAsterisk}}}},
{
{
{
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {
memgraph::auth::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<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;
#ifdef MG_ENTERPRISE
std::vector<std::vector<memgraph::query::TypedValue>> 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::utils::license::global_license_checker.IsValidLicenseFast()) {
fine_grained_grants = ShowFineGrainedUserPrivileges(user);
}
#endif
} else {
grants = ShowRolePrivileges(role);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
fine_grained_grants = ShowFineGrainedRolePrivileges(role);
}
#endif
}
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
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<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#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<memgraph::query::AuthQuery::Privilege> &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<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#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 <class TEditPermissionsFun
#ifdef MG_ENTERPRISE
,
class TEditFineGrainedPermissionsFun
#endif
>
void AuthQueryHandler::EditPermissions(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#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<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);
}
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
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::utils::license::global_license_checker.IsValidLicenseFast()) {
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);
}
}
#endif
locked_auth->SaveRole(*role);
}
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
} // namespace memgraph::glue

109
src/glue/auth_handler.hpp Normal file
View File

@ -0,0 +1,109 @@
// 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
#ifdef MG_ENTERPRISE
,
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
#endif
) override;
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override;
void RevokePrivilege(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#endif
) override;
private:
template <class TEditPermissionsFun
#ifdef MG_ENTERPRISE
,
class TEditFineGrainedPermissionsFun
#endif
>
void EditPermissions(
const std::string &user_or_role, const std::vector<memgraph::query::AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#endif
,
const TEditPermissionsFun &edit_permissions_fun
#ifdef MG_ENTERPRISE
,
const TEditFineGrainedPermissionsFun &edit_fine_grained_permissions_fun
#endif
);
};
} // namespace memgraph::glue

View File

@ -19,6 +19,7 @@
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <regex>
#include <string>
@ -32,9 +33,12 @@
#include <spdlog/sinks/dist_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include "auth/models.hpp"
#include "communication/bolt/v1/constants.hpp"
#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"
@ -462,392 +466,10 @@ struct SessionData {
std::optional<std::string> run_id;
};
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);
}
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) override {
EditPermissions(user_or_role, 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);
});
}
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, 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);
});
}
void RevokePrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, 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);
});
}
private:
template <class TEditFun>
void EditPermissions(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, const TEditFun &edit_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_fun(&user->permissions(), permission);
}
locked_auth->SaveUser(*user);
} else {
for (const auto &permission : permissions) {
edit_fun(&role->permissions(), permission);
}
locked_auth->SaveRole(*role);
}
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
};
class AuthChecker final : public memgraph::query::AuthChecker {
public:
explicit AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: auth_{auth} {}
static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const auto user_permissions = user.GetPermissions();
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const final {
std::optional<memgraph::auth::User> maybe_user;
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream> {
public:
@ -891,7 +513,7 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
#endif
try {
auto result = interpreter_.Prepare(query, params_pv, username);
if (user_ && !AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
interpreter_.Abort();
throw memgraph::communication::bolt::ClientError(
"You are not authorized to execute this query! Please contact "
@ -1251,8 +873,8 @@ 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);
AuthChecker auth_checker{&auth};
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;

View File

@ -11,19 +11,97 @@
#pragma once
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
#include "storage/v2/id_types.hpp"
namespace memgraph::query {
class FineGrainedAuthChecker;
class AuthChecker {
public:
virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
virtual ~AuthChecker() = default;
[[nodiscard]] virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
#ifdef MG_ENTERPRISE
[[nodiscard]] virtual std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string &username, const memgraph::query::DbAccessor *db_accessor) const = 0;
#endif
};
#ifdef MG_ENTERPRISE
class FineGrainedAuthChecker {
public:
virtual ~FineGrainedAuthChecker() = default;
[[nodiscard]] virtual bool Has(const query::VertexAccessor &vertex, memgraph::storage::View view,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool Has(const query::EdgeAccessor &edge,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool Has(const std::vector<memgraph::storage::LabelId> &labels,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool Has(const memgraph::storage::EdgeTypeId &edge_type,
query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool HasGlobalPrivilegeOnVertices(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
[[nodiscard]] virtual bool HasGlobalPrivilegeOnEdges(
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
};
class AllowEverythingAuthChecker final : public query::AuthChecker {
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const override {
class AllowEverythingFineGrainedAuthChecker final : public query::FineGrainedAuthChecker {
public:
bool Has(const VertexAccessor & /*vertex*/, const memgraph::storage::View /*view*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
};
} // namespace memgraph::query
bool Has(const memgraph::query::EdgeAccessor & /*edge*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool Has(const std::vector<memgraph::storage::LabelId> & /*labels*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool Has(const memgraph::storage::EdgeTypeId & /*edge_type*/,
const query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool HasGlobalPrivilegeOnVertices(
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
bool HasGlobalPrivilegeOnEdges(
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
return true;
}
}; // namespace memgraph::query
#endif
class AllowEverythingAuthChecker final : public query::AuthChecker {
public:
bool IsUserAuthorized(const std::optional<std::string> & /*username*/,
const std::vector<query::AuthQuery::Privilege> & /*privileges*/) const override {
return true;
}
#ifdef MG_ENTERPRISE
std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(const std::string & /*username*/,
const query::DbAccessor * /*dba*/) const override {
return std::make_unique<AllowEverythingFineGrainedAuthChecker>();
}
#endif
}; // namespace memgraph::query
} // namespace memgraph::query

View File

@ -11,6 +11,7 @@
#pragma once
#include <memory>
#include <type_traits>
#include "query/common.hpp"
@ -72,6 +73,9 @@ struct ExecutionContext {
ExecutionStats execution_stats;
TriggerContextCollector *trigger_context_collector{nullptr};
utils::AsyncTimer timer;
#ifdef MG_ENTERPRISE
std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};
#endif
};
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");

View File

@ -2236,6 +2236,7 @@ cpp<#
(:serialize (:slk))
(:clone))
(lcp:define-class auth-query (query)
((action "Action" :scope :public)
(user "std::string" :scope :public)
@ -2244,7 +2245,9 @@ cpp<#
(password "Expression *" :initval "nullptr" :scope :public
:slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression"))
(privileges "std::vector<Privilege>" :scope :public))
(privileges "std::vector<Privilege>" :scope :public)
(label-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public)
(edge-type-privileges "std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>>" :scope :public))
(:public
(lcp:define-enum action
(create-role drop-role show-roles create-user set-password drop-user
@ -2257,6 +2260,9 @@ cpp<#
dump replication durability read_file free_memory trigger config stream module_read module_write
websocket)
(:serialize))
(lcp:define-enum fine-grained-privilege
(nothing read update create_delete)
(:serialize))
#>cpp
AuthQuery() = default;
@ -2266,13 +2272,17 @@ cpp<#
#>cpp
AuthQuery(Action action, std::string user, std::string role,
std::string user_or_role, Expression *password,
std::vector<Privilege> privileges)
std::vector<Privilege> privileges,
std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> label_privileges,
std::vector<std::unordered_map<FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges)
: action_(action),
user_(user),
role_(role),
user_or_role_(user_or_role),
password_(password),
privileges_(privileges) {}
privileges_(privileges),
label_privileges_(label_privileges),
edge_type_privileges_(edge_type_privileges) {}
cpp<#)
(:private
#>cpp

View File

@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "query/frontend/ast/cypher_main_visitor.hpp"
#include <support/Any.h>
#include <algorithm>
#include <climits>
@ -44,6 +45,8 @@ namespace memgraph::query::frontend {
const std::string CypherMainVisitor::kAnonPrefix = "anon";
namespace {
enum class EntityType : uint8_t { LABELS, EDGE_TYPES };
template <typename TVisitor>
std::optional<std::pair<memgraph::query::Expression *, size_t>> VisitMemoryLimit(
MemgraphCypher::MemoryLimitContext *memory_limit_ctx, TVisitor *visitor) {
@ -1275,10 +1278,14 @@ antlrcpp::Any CypherMainVisitor::visitGrantPrivilege(MemgraphCypher::GrantPrivil
AuthQuery *auth = storage_->Create<AuthQuery>();
auth->action_ = AuthQuery::Action::GRANT_PRIVILEGE;
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
if (ctx->grantPrivilegesList()) {
const auto [label_privileges, edge_type_privileges, privileges] = std::any_cast<
std::tuple<std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<memgraph::query::AuthQuery::Privilege>>>(ctx->grantPrivilegesList()->accept(this));
auth->label_privileges_ = label_privileges;
auth->edge_type_privileges_ = edge_type_privileges;
auth->privileges_ = privileges;
} else {
/* grant all privileges */
auth->privileges_ = kPrivilegesAll;
@ -1293,10 +1300,8 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(MemgraphCypher::DenyPrivileg
AuthQuery *auth = storage_->Create<AuthQuery>();
auth->action_ = AuthQuery::Action::DENY_PRIVILEGE;
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
if (ctx->privilegesList()) {
auth->privileges_ = std::any_cast<std::vector<AuthQuery::Privilege>>(ctx->privilegesList()->accept(this));
} else {
/* deny all privileges */
auth->privileges_ = kPrivilegesAll;
@ -1304,6 +1309,50 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(MemgraphCypher::DenyPrivileg
return auth;
}
/**
* @return AuthQuery*
*/
antlrcpp::Any CypherMainVisitor::visitPrivilegesList(MemgraphCypher::PrivilegesListContext *ctx) {
std::vector<AuthQuery::Privilege> privileges{};
for (const auto &privilege : ctx->privilege()) {
privileges.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
return privileges;
}
/**
* @return std::tuple<std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<memgraph::query::AuthQuery::Privilege>>
*/
antlrcpp::Any CypherMainVisitor::visitGrantPrivilegesList(MemgraphCypher::GrantPrivilegesListContext *ctx) {
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges;
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges;
std::vector<memgraph::query::AuthQuery::Privilege> privileges;
for (auto *it : ctx->privilegeOrEntityPrivileges()) {
if (it->entityPrivilegeList()) {
const auto result =
std::any_cast<std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>>(
it->entityPrivilegeList()->accept(this));
if (!result.first.empty()) {
label_privileges.emplace_back(result.first);
}
if (!result.second.empty()) {
edge_type_privileges.emplace_back(result.second);
}
} else {
privileges.push_back(std::any_cast<AuthQuery::Privilege>(it->privilege()->accept(this)));
}
}
return std::tuple<std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>,
std::vector<memgraph::query::AuthQuery::Privilege>>(label_privileges, edge_type_privileges,
privileges);
}
/**
* @return AuthQuery*
*/
@ -1311,9 +1360,22 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(MemgraphCypher::RevokePriv
AuthQuery *auth = storage_->Create<AuthQuery>();
auth->action_ = AuthQuery::Action::REVOKE_PRIVILEGE;
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
if (ctx->revokePrivilegesList()) {
for (auto *it : ctx->revokePrivilegesList()->privilegeOrEntities()) {
if (it->entitiesList()) {
const auto entity_type = std::any_cast<EntityType>(it->entityType()->accept(this));
if (entity_type == EntityType::LABELS) {
auth->label_privileges_.push_back(
{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE,
std::any_cast<std::vector<std::string>>(it->entitiesList()->accept(this))}});
} else {
auth->edge_type_privileges_.push_back(
{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE,
std::any_cast<std::vector<std::string>>(it->entitiesList()->accept(this))}});
}
} else {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(it->privilege()->accept(this)));
}
}
} else {
/* revoke all privileges */
@ -1322,6 +1384,48 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(MemgraphCypher::RevokePriv
return auth;
}
/**
* @return std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
*/
antlrcpp::Any CypherMainVisitor::visitEntityPrivilegeList(MemgraphCypher::EntityPrivilegeListContext *ctx) {
std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
result;
for (auto *it : ctx->entityPrivilege()) {
const auto key = std::any_cast<AuthQuery::FineGrainedPrivilege>(it->granularPrivilege()->accept(this));
const auto entityType = std::any_cast<EntityType>(it->entityType()->accept(this));
auto value = std::any_cast<std::vector<std::string>>(it->entitiesList()->accept(this));
switch (entityType) {
case EntityType::LABELS:
result.first[key] = std::move(value);
break;
case EntityType::EDGE_TYPES:
result.second[key] = std::move(value);
break;
}
}
return result;
}
/**
* @return std::vector<std::string>
*/
antlrcpp::Any CypherMainVisitor::visitEntitiesList(MemgraphCypher::EntitiesListContext *ctx) {
std::vector<std::string> entities;
if (ctx->listOfEntities()) {
for (auto *entity : ctx->listOfEntities()->entity()) {
entities.push_back(std::any_cast<std::string>(entity->symbolicName()->accept(this)));
}
} else {
entities.emplace_back("*");
}
return entities;
}
/**
* @return AuthQuery::Privilege
*/
@ -1350,6 +1454,26 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
LOG_FATAL("Should not get here - unknown privilege!");
}
/**
* @return AuthQuery::FineGrainedPrivilege
*/
antlrcpp::Any CypherMainVisitor::visitGranularPrivilege(MemgraphCypher::GranularPrivilegeContext *ctx) {
if (ctx->NOTHING()) return AuthQuery::FineGrainedPrivilege::NOTHING;
if (ctx->READ()) return AuthQuery::FineGrainedPrivilege::READ;
if (ctx->UPDATE()) return AuthQuery::FineGrainedPrivilege::UPDATE;
if (ctx->CREATE_DELETE()) return AuthQuery::FineGrainedPrivilege::CREATE_DELETE;
LOG_FATAL("Should not get here - unknown fine grained privilege!");
}
/**
* @return EntityType
*/
antlrcpp::Any CypherMainVisitor::visitEntityType(MemgraphCypher::EntityTypeContext *ctx) {
if (ctx->LABELS()) return EntityType::LABELS;
if (ctx->EDGE_TYPES()) return EntityType::EDGE_TYPES;
LOG_FATAL("Should not get here - unknown entity type!");
}
/**
* @return AuthQuery*
*/
@ -2138,8 +2262,8 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
}
auto is_user_defined_function = [](const std::string &function_name) {
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be user-defined
// functions. Builtin functions should be case insensitive.
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be
// user-defined functions. Builtin functions should be case insensitive.
return function_name.find('.') != std::string::npos;
};
@ -2155,8 +2279,8 @@ antlrcpp::Any CypherMainVisitor::visitFunctionInvocation(MemgraphCypher::Functio
antlrcpp::Any CypherMainVisitor::visitFunctionName(MemgraphCypher::FunctionNameContext *ctx) {
auto function_name = ctx->getText();
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be user-defined
// functions. Builtin functions should be case insensitive.
// Dots are present only in user-defined functions, since modules are case-sensitive, so must be
// user-defined functions. Builtin functions should be case insensitive.
if (function_name.find('.') != std::string::npos) {
return function_name;
}

View File

@ -453,6 +453,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitClearRole(MemgraphCypher::ClearRoleContext *ctx) override;
void extractPrivilege(AuthQuery *auth, antlropencypher::MemgraphCypher::PrivilegeContext *privilege);
/**
* @return AuthQuery*
*/
@ -463,11 +465,42 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitDenyPrivilege(MemgraphCypher::DenyPrivilegeContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitGrantPrivilegesList(MemgraphCypher::GrantPrivilegesListContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitPrivilegesList(MemgraphCypher::PrivilegesListContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitRevokePrivilege(MemgraphCypher::RevokePrivilegeContext *ctx) override;
/**
* @return std::pair<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>,
std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>
*/
antlrcpp::Any visitEntityPrivilegeList(MemgraphCypher::EntityPrivilegeListContext *ctx) override;
/**
* @return std::vector<std::string>
*/
antlrcpp::Any visitEntitiesList(MemgraphCypher::EntitiesListContext *ctx) override;
/**
* @return AuthQuery::FineGrainedPrivilege
*/
antlrcpp::Any visitGranularPrivilege(MemgraphCypher::GranularPrivilegeContext *ctx) override;
/**
* @return std::string
*/
antlrcpp::Any visitEntityType(MemgraphCypher::EntityTypeContext *ctx) override;
/**
* @return AuthQuery::Privilege
*/

View File

@ -37,6 +37,7 @@ memgraphCypherKeyword : cypherKeyword
| CONFIG
| CONFIGS
| CONSUMER_GROUP
| CREATE_DELETE
| CREDENTIALS
| CSV
| DATA
@ -45,6 +46,7 @@ memgraphCypherKeyword : cypherKeyword
| DENY
| DROP
| DUMP
| EDGE_TYPES
| EXECUTE
| FOR
| FOREACH
@ -56,6 +58,7 @@ memgraphCypherKeyword : cypherKeyword
| IDENTIFIED
| ISOLATION
| KAFKA
| LABELS
| LEVEL
| LOAD
| LOCK
@ -63,6 +66,7 @@ memgraphCypherKeyword : cypherKeyword
| MODE
| NEXT
| NO
| NOTHING
| PASSWORD
| PULSAR
| PORT
@ -228,11 +232,11 @@ setRole : SET ROLE FOR user=userOrRoleName TO role=userOrRoleName;
clearRole : CLEAR ROLE FOR user=userOrRoleName ;
grantPrivilege : GRANT ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
grantPrivilege : GRANT ( ALL PRIVILEGES | privileges=grantPrivilegesList ) TO userOrRole=userOrRoleName ;
denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole=userOrRoleName ;
denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegesList ) TO userOrRole=userOrRoleName ;
revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ;
revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=revokePrivilegesList ) FROM userOrRole=userOrRoleName ;
privilege : CREATE
| DELETE
@ -257,7 +261,29 @@ privilege : CREATE
| WEBSOCKET
;
privilegeList : privilege ( ',' privilege )* ;
granularPrivilege : NOTHING | READ | UPDATE | CREATE_DELETE ;
entityType : LABELS | EDGE_TYPES ;
privilegeOrEntityPrivileges : privilege | entityPrivileges=entityPrivilegeList ;
grantPrivilegesList : privilegeOrEntityPrivileges ( ',' privilegeOrEntityPrivileges )* ;
entityPrivilegeList : entityPrivilege ( ',' entityPrivilege )* ;
entityPrivilege : granularPrivilege ON entityType entities=entitiesList ;
privilegeOrEntities : privilege | entityType entities=entitiesList ;
revokePrivilegesList : privilegeOrEntities ( ',' privilegeOrEntities )* ;
privilegesList : privilege ( ',' privilege )* ;
entitiesList : ASTERISK | listOfEntities ;
listOfEntities : entity ( ',' entity )* ;
entity : COLON symbolicName ;
showPrivileges : SHOW PRIVILEGES FOR userOrRole=userOrRoleName ;

View File

@ -42,6 +42,7 @@ COMMITTED : C O M M I T T E D ;
CONFIG : C O N F I G ;
CONFIGS : C O N F I G S;
CONSUMER_GROUP : C O N S U M E R UNDERSCORE G R O U P ;
CREATE_DELETE : C R E A T E UNDERSCORE D E L E T E ;
CREDENTIALS : C R E D E N T I A L S ;
CSV : C S V ;
DATA : D A T A ;
@ -66,6 +67,7 @@ IDENTIFIED : I D E N T I F I E D ;
IGNORE : I G N O R E ;
ISOLATION : I S O L A T I O N ;
KAFKA : K A F K A ;
LABELS : L A B E L S ;
LEVEL : L E V E L ;
LOAD : L O A D ;
LOCK : L O C K ;
@ -75,6 +77,7 @@ MODULE_READ : M O D U L E UNDERSCORE R E A D ;
MODULE_WRITE : M O D U L E UNDERSCORE W R I T E ;
NEXT : N E X T ;
NO : N O ;
NOTHING : N O T H I N G ;
PASSWORD : P A S S W O R D ;
PORT : P O R T ;
PRIVILEGES : P R I V I L E G E S ;
@ -114,3 +117,4 @@ USER : U S E R ;
USERS : U S E R S ;
VERSION : V E R S I O N ;
WEBSOCKET : W E B S O C K E T ;
EDGE_TYPES : E D G E UNDERSCORE T Y P E S ;

View File

@ -205,8 +205,10 @@ const trie::Trie kKeywords = {"union",
"service_url",
"version",
"config",
"websocket"
"foreach"};
"websocket",
"foreach",
"labels",
"edge_types"};
// Unicode codepoints that are allowed at the start of the unescaped name.
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(

View File

@ -20,8 +20,10 @@
#include <functional>
#include <limits>
#include <optional>
#include <unordered_map>
#include <variant>
#include "auth/models.hpp"
#include "glue/communication.hpp"
#include "memory/memory_control.hpp"
#include "query/constants.hpp"
@ -44,8 +46,9 @@
#include "query/stream/common.hpp"
#include "query/trigger.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/edge.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/replication/enums.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
@ -263,6 +266,7 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
private:
storage::Storage *db_;
};
/// returns false if the replication role can't be set
/// @throw QueryRuntimeException if an error ocurred.
@ -277,6 +281,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::View::OLD);
@ -284,6 +289,12 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
std::string rolename = auth_query->role_;
std::string user_or_role = auth_query->user_or_role_;
std::vector<AuthQuery::Privilege> privileges = auth_query->privileges_;
#ifdef MG_ENTERPRISE
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges =
auth_query->label_privileges_;
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges =
auth_query->edge_type_privileges_;
#endif
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
Callback callback;
@ -313,7 +324,19 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
// If the license is not valid we create users with admin access
if (!valid_enterprise_license) {
spdlog::warn("Granting all the privileges to {}.", username);
auth->GrantPrivilege(username, kPrivilegesAll);
auth->GrantPrivilege(username, kPrivilegesAll
#ifdef MG_ENTERPRISE
,
{{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE, {auth::kAsterisk}}}},
{
{
{
AuthQuery::FineGrainedPrivilege::CREATE_DELETE, { auth::kAsterisk }
}
}
}
#endif
);
}
return std::vector<std::vector<TypedValue>>();
@ -388,8 +411,18 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
};
return callback;
case AuthQuery::Action::GRANT_PRIVILEGE:
callback.fn = [auth, user_or_role, privileges] {
auth->GrantPrivilege(user_or_role, privileges);
callback.fn = [auth, user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
] {
auth->GrantPrivilege(user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
);
return std::vector<std::vector<TypedValue>>();
};
return callback;
@ -400,8 +433,18 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
};
return callback;
case AuthQuery::Action::REVOKE_PRIVILEGE: {
callback.fn = [auth, user_or_role, privileges] {
auth->RevokePrivilege(user_or_role, privileges);
callback.fn = [auth, user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
] {
auth->RevokePrivilege(user_or_role, privileges
#ifdef MG_ENTERPRISE
,
label_privileges, edge_type_privileges
#endif
);
return std::vector<std::vector<TypedValue>>();
};
return callback;
@ -433,7 +476,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
default:
break;
}
}
} // namespace
Callback HandleReplicationQuery(ReplicationQuery *repl_query, const Parameters &parameters,
InterpreterContext *interpreter_context, DbAccessor *db_accessor,
@ -932,7 +975,7 @@ struct PullPlanVector {
struct PullPlan {
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters &parameters, bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
TriggerContextCollector *trigger_context_collector = nullptr,
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector = nullptr,
std::optional<size_t> memory_limit = {});
std::optional<plan::ProfilingStatsWithTotalTime> Pull(AnyStream *stream, std::optional<int> n,
const std::vector<Symbol> &output_symbols,
@ -961,7 +1004,8 @@ struct PullPlan {
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &parameters, const bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit)
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector,
const std::optional<size_t> memory_limit)
: plan_(plan),
cursor_(plan->plan().MakeCursor(execution_memory)),
frame_(plan->symbol_table().max_position(), execution_memory),
@ -972,6 +1016,11 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par
ctx_.evaluation_context.parameters = parameters;
ctx_.evaluation_context.properties = NamesToProperties(plan->ast_storage().properties_, dba);
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, dba);
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && username.has_value() && dba) {
ctx_.auth_checker = interpreter_context->auth_checker->GetFineGrainedAuthChecker(*username, dba);
}
#endif
if (interpreter_context->config.execution_timeout_sec > 0) {
ctx_.timer = utils::AsyncTimer{interpreter_context->config.execution_timeout_sec};
}
@ -1145,6 +1194,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
InterpreterContext *interpreter_context, DbAccessor *dba,
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications,
const std::string *username,
TriggerContextCollector *trigger_context_collector = nullptr) {
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query);
@ -1153,6 +1203,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
EvaluationContext evaluation_context;
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parsed_query.parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::View::OLD);
const auto memory_limit = EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
if (memory_limit) {
@ -1188,8 +1239,9 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
header.push_back(
utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first);
}
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, trigger_context_collector, memory_limit);
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory,
StringPointerToOptional(username), trigger_context_collector, memory_limit);
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
[pull_plan = std::move(pull_plan), output_symbols = std::move(output_symbols), summary](
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
@ -1249,7 +1301,8 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
DbAccessor *dba, utils::MemoryResource *execution_memory) {
DbAccessor *dba, utils::MemoryResource *execution_memory,
const std::string *username) {
const std::string kProfileQueryStart = "profile ";
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kProfileQueryStart),
@ -1299,12 +1352,14 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage), cypher_query,
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
auto rw_type_checker = plan::ReadWriteTypeChecker();
auto optional_username = StringPointerToOptional(username);
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
return PreparedQuery{{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"},
std::move(parsed_query.required_privileges),
[plan = std::move(cypher_query_plan), parameters = std::move(parsed_inner_query.parameters),
summary, dba, interpreter_context, execution_memory, memory_limit,
summary, dba, interpreter_context, execution_memory, memory_limit, optional_username,
// We want to execute the query we are profiling lazily, so we delay
// the construction of the corresponding context.
stats_and_total_time = std::optional<plan::ProfilingStatsWithTotalTime>{},
@ -1313,7 +1368,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
// No output symbols are given so that nothing is streamed.
if (!stats_and_total_time) {
stats_and_total_time = PullPlan(plan, parameters, true, dba, interpreter_context,
execution_memory, nullptr, memory_limit)
execution_memory, optional_username, nullptr, memory_limit)
.Pull(stream, {}, {}, summary);
pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time));
}
@ -1469,7 +1524,7 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
DbAccessor *dba, utils::MemoryResource *execution_memory) {
DbAccessor *dba, utils::MemoryResource *execution_memory, const std::string *username) {
if (in_explicit_transaction) {
throw UserModificationInMulticommandTxException();
}
@ -1489,8 +1544,8 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
[fn = callback.fn](Frame *, ExecutionContext *) { return fn(); }),
0.0, AstStorage{}, symbol_table));
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory);
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, StringPointerToOptional(username));
return PreparedQuery{
callback.header, std::move(parsed_query.required_privileges),
[pull_plan = std::move(pull_plan), callback = std::move(callback), output_symbols = std::move(output_symbols),
@ -2288,7 +2343,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
&*execution_db_accessor_, &query_execution->execution_memory,
&query_execution->notifications,
&query_execution->notifications, username,
trigger_context_collector_ ? &*trigger_context_collector_ : nullptr);
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
@ -2296,7 +2351,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, &*execution_db_accessor_,
&query_execution->execution_memory_with_exception);
&query_execution->execution_memory_with_exception, username);
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
&query_execution->execution_memory);
@ -2306,7 +2361,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, &*execution_db_accessor_,
&query_execution->execution_memory_with_exception);
&query_execution->execution_memory_with_exception, username);
} else if (utils::Downcast<InfoQuery>(parsed_query.query)) {
prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, interpreter_context_->db,

View File

@ -99,14 +99,33 @@ class AuthQueryHandler {
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
virtual void GrantPrivilege(
const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#endif
) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void RevokePrivilege(const std::string &user_or_role,
const std::vector<AuthQuery::Privilege> &privileges) = 0;
virtual void RevokePrivilege(
const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges
#ifdef MG_ENTERPRISE
,
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
#endif
) = 0;
};
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
@ -173,6 +192,13 @@ struct InterpreterContext {
storage::Storage *db;
// ANTLR has singleton instance that is shared between threads. It is
// protected by locks inside of ANTLR. Unfortunately, they are not protected
// in a very good way. Once we have ANTLR version without race conditions we
// can remove this lock. This will probably never happen since ANTLR
// developers introduce more bugs in each version. Fortunately, we have
// cache so this lock probably won't impact performance much...
utils::SpinLock antlr_lock;
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
std::atomic<bool> is_shutting_down{false};

View File

@ -25,7 +25,9 @@
#include <cppitertools/chain.hpp>
#include <cppitertools/imap.hpp>
#include "spdlog/spdlog.h"
#include "query/auth_checker.hpp"
#include "query/context.hpp"
#include "query/db_accessor.hpp"
#include "query/exceptions.hpp"
@ -39,11 +41,13 @@
#include "query/procedure/mg_procedure_impl.hpp"
#include "query/procedure/module.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/view.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
#include "utils/exceptions.hpp"
#include "utils/fnv.hpp"
#include "utils/license.hpp"
#include "utils/likely.hpp"
#include "utils/logging.hpp"
#include "utils/memory.hpp"
@ -237,6 +241,13 @@ CreateNode::CreateNodeCursor::CreateNodeCursor(const CreateNode &self, utils::Me
bool CreateNode::CreateNodeCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("CreateNode");
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(self_.node_info_.labels,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Vertex not created due to not having enough permission!");
}
#endif
if (input_cursor_->Pull(frame, context)) {
auto created_vertex = CreateLocalVertex(self_.node_info_, &frame, context);
@ -322,6 +333,21 @@ bool CreateExpand::CreateExpandCursor::Pull(Frame &frame, ExecutionContext &cont
if (!input_cursor_->Pull(frame, context)) return false;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast()) {
const auto fine_grained_permission = self_.existing_node_
? memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE
: memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE;
if (context.auth_checker &&
!(context.auth_checker->Has(self_.edge_info_.edge_type,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(self_.node_info_.labels, fine_grained_permission))) {
throw QueryRuntimeException("Edge not created due to not having enough permission!");
}
}
#endif
// get the origin vertex
TypedValue &vertex_value = frame[self_.input_symbol_];
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
@ -406,12 +432,31 @@ class ScanAllCursor : public Cursor {
vertices_.emplace(std::move(next_vertices.value()));
vertices_it_.emplace(vertices_.value().begin());
}
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!FindNextVertex(context)) {
return false;
}
#endif
frame[output_symbol_] = *vertices_it_.value();
++vertices_it_.value();
return true;
}
#ifdef MG_ENTERPRISE
bool FindNextVertex(const ExecutionContext &context) {
while (vertices_it_.value() != vertices_.value().end()) {
if (context.auth_checker->Has(*vertices_it_.value(), memgraph::storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
return true;
}
++vertices_it_.value();
}
return false;
}
#endif
void Shutdown() override { input_cursor_->Shutdown(); }
void Reset() override {
@ -685,6 +730,15 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
auto edge = *(*in_edges_it_)++;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.From(), self_.view_,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::IN);
return true;
@ -697,6 +751,14 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
// we should do only one expansion for cycles, and it was
// already done in the block above
if (self_.common_.direction == EdgeAtom::Direction::BOTH && edge.IsCycle()) continue;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.To(), self_.view_,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
@ -834,6 +896,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio
chain_elements.emplace_back(wrapper(EdgeAtom::Direction::IN, std::move(edges)));
}
}
if (direction != EdgeAtom::Direction::IN) {
auto edges = UnwrapEdgesResult(vertex.OutEdges(view, edge_types));
if (edges.begin() != edges.end()) {
@ -1017,16 +1080,23 @@ class ExpandVariableCursor : public Cursor {
// if we are here, we have a valid stack,
// get the edge, increase the relevant iterator
auto current_edge = *edges_it_.back()++;
// Check edge-uniqueness.
bool found_existing =
std::any_of(edges_on_frame.begin(), edges_on_frame.end(),
[&current_edge](const TypedValue &edge) { return current_edge.first == edge.ValueEdge(); });
if (found_existing) continue;
AppendEdge(current_edge.first, &edges_on_frame);
VertexAccessor current_vertex =
current_edge.second == EdgeAtom::Direction::IN ? current_edge.first.From() : current_edge.first.To();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(current_edge.first, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(current_vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
AppendEdge(current_edge.first, &edges_on_frame);
if (!self_.common_.existing_node) {
frame[self_.common_.node_symbol] = current_vertex;
@ -1187,6 +1257,15 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(edge.To(), edge, frame, evaluator) && !Contains(in_edge, edge.To())) {
in_edge.emplace(edge.To(), edge);
if (Contains(out_edge, edge.To())) {
@ -1204,6 +1283,15 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(edge.From(), edge, frame, evaluator) && !Contains(in_edge, edge.From())) {
in_edge.emplace(edge.From(), edge);
if (Contains(out_edge, edge.From())) {
@ -1235,6 +1323,14 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(vertex, edge, frame, evaluator) && !Contains(out_edge, edge.To())) {
out_edge.emplace(edge.To(), edge);
if (Contains(in_edge, edge.To())) {
@ -1252,6 +1348,14 @@ class STShortestPathCursor : public query::plan::Cursor {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
if (ShouldExpand(vertex, edge, frame, evaluator) && !Contains(out_edge, edge.From())) {
out_edge.emplace(edge.From(), edge);
if (Contains(in_edge, edge.From())) {
@ -1298,10 +1402,17 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
// for the given (edge, vertex) pair checks if they satisfy the
// "where" condition. if so, places them in the to_visit_ structure.
auto expand_pair = [this, &evaluator, &frame](EdgeAccessor edge, VertexAccessor vertex) {
auto expand_pair = [this, &evaluator, &frame, &context](EdgeAccessor edge, VertexAccessor vertex) {
// if we already processed the given vertex it doesn't get expanded
if (processed_.find(vertex) != processed_.end()) return;
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
return;
}
#endif
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
frame[self_.filter_lambda_.inner_node_symbol] = vertex;
@ -1364,6 +1475,7 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
const auto &vertex = vertex_value.ValueVertex();
processed_.emplace(vertex, std::nullopt);
expand_from_vertex(vertex);
// go back to loop start and see if we expanded anything
@ -1475,9 +1587,18 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
// For the given (edge, vertex, weight, depth) tuple checks if they
// satisfy the "where" condition. if so, places them in the priority
// queue.
auto expand_pair = [this, &evaluator, &frame, &create_state](const EdgeAccessor &edge, const VertexAccessor &vertex,
const TypedValue &total_weight, int64_t depth) {
auto expand_pair = [this, &evaluator, &frame, &create_state, &context](
const EdgeAccessor &edge, const VertexAccessor &vertex, const TypedValue &total_weight,
int64_t depth) {
auto *memory = evaluator.GetMemoryResource();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
return;
}
#endif
if (self_.filter_lambda_.expression) {
frame[self_.filter_lambda_.inner_edge_symbol] = edge;
frame[self_.filter_lambda_.inner_node_symbol] = vertex;
@ -1775,17 +1896,33 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
// Populates the priority queue structure with expansions
// from the given vertex. skips expansions that don't satisfy
// the "where" condition.
auto expand_from_vertex = [this, &expand_vertex](const VertexAccessor &vertex, const TypedValue &weight,
int64_t depth) {
auto expand_from_vertex = [this, &expand_vertex, &context](const VertexAccessor &vertex, const TypedValue &weight,
int64_t depth) {
if (self_.common_.direction != EdgeAtom::Direction::IN) {
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : out_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge.To(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
expand_vertex(edge, EdgeAtom::Direction::OUT, weight, depth);
}
}
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
for (const auto &edge : in_edges) {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(edge.From(), storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ) &&
context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) {
continue;
}
#endif
expand_vertex(edge, EdgeAtom::Direction::IN, weight, depth);
}
}
@ -1808,8 +1945,8 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
// Check if there is an external error.
if (MustAbort(context)) throw HintedAbortError();
// If traversal stack if filled, the DFS traversal tree is created. Traverse the tree iteratively by preserving
// the traversal state on stack.
// If traversal stack if filled, the DFS traversal tree is created. Traverse the tree iteratively by
// preserving the traversal state on stack.
while (!traversal_stack_.empty()) {
auto &current_level = traversal_stack_.back();
auto &edges_on_frame = frame[self_.common_.edge_symbol].ValueList();
@ -2227,7 +2364,16 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
for (TypedValue &expression_result : expression_results) {
if (MustAbort(context)) throw HintedAbortError();
if (expression_result.type() == TypedValue::Type::Edge) {
auto maybe_value = dba.RemoveEdge(&expression_result.ValueEdge());
auto &ea = expression_result.ValueEdge();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!(context.auth_checker->Has(ea, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE) &&
context.auth_checker->Has(ea.To(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
context.auth_checker->Has(ea.From(), storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::UPDATE))) {
throw QueryRuntimeException("Edge not deleted due to not having enough permission!");
}
#endif
auto maybe_value = dba.RemoveEdge(&ea);
if (maybe_value.HasError()) {
switch (maybe_value.GetError()) {
case storage::Error::SERIALIZATION_ERROR:
@ -2252,6 +2398,12 @@ bool Delete::DeleteCursor::Pull(Frame &frame, ExecutionContext &context) {
switch (expression_result.type()) {
case TypedValue::Type::Vertex: {
auto &va = expression_result.ValueVertex();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(va, storage::View::NEW, query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Vertex not deleted due to not having enough permission!");
}
#endif
if (self_.detach_) {
auto res = dba.DetachRemoveVertex(&va);
if (res.HasError()) {
@ -2355,6 +2507,13 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
switch (lhs.type()) {
case TypedValue::Type::Vertex: {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueVertex(), storage::View::NEW,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Vertex property not set due to not having enough permission!");
}
#endif
auto old_value = PropsSetChecked(&lhs.ValueVertex(), self_.property_, rhs);
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
if (context.trigger_context_collector) {
@ -2365,10 +2524,17 @@ bool SetProperty::SetPropertyCursor::Pull(Frame &frame, ExecutionContext &contex
break;
}
case TypedValue::Type::Edge: {
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueEdge(), memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Edge property not set due to not having enough permission!");
}
#endif
auto old_value = PropsSetChecked(&lhs.ValueEdge(), self_.property_, rhs);
context.execution_stats[ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
if (context.trigger_context_collector) {
// rhs cannot be moved because it was created with the allocator that is only valid during current pull
// rhs cannot be moved because it was created with the allocator that is only valid
// during current pull
context.trigger_context_collector->RegisterSetObjectProperty(lhs.ValueEdge(), self_.property_,
TypedValue{std::move(old_value)}, TypedValue{rhs});
}
@ -2557,9 +2723,23 @@ bool SetProperties::SetPropertiesCursor::Pull(Frame &frame, ExecutionContext &co
switch (lhs.type()) {
case TypedValue::Type::Vertex:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueVertex(), storage::View::NEW,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Vertex properties not set due to not having enough permission!");
}
#endif
SetPropertiesOnRecord(&lhs.ValueVertex(), rhs, self_.op_, &context);
break;
case TypedValue::Type::Edge:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueEdge(), memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Edge properties not set due to not having enough permission!");
}
#endif
SetPropertiesOnRecord(&lhs.ValueEdge(), rhs, self_.op_, &context);
break;
case TypedValue::Type::Null:
@ -2597,6 +2777,13 @@ SetLabels::SetLabelsCursor::SetLabelsCursor(const SetLabels &self, utils::Memory
bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("SetLabels");
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(self_.labels_, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Couldn't set label due to not having enough permission!");
}
#endif
if (!input_cursor_->Pull(frame, context)) return false;
TypedValue &vertex_value = frame[self_.input_symbol_];
@ -2604,6 +2791,15 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
if (vertex_value.IsNull()) return true;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.ValueVertex();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Couldn't set label due to not having enough permission!");
}
#endif
for (auto label : self_.labels_) {
auto maybe_value = vertex.AddLabel(label);
if (maybe_value.HasError()) {
@ -2686,9 +2882,22 @@ bool RemoveProperty::RemovePropertyCursor::Pull(Frame &frame, ExecutionContext &
switch (lhs.type()) {
case TypedValue::Type::Vertex:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueVertex(), storage::View::NEW,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Vertex property not removed due to not having enough permission!");
}
#endif
remove_prop(&lhs.ValueVertex());
break;
case TypedValue::Type::Edge:
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(lhs.ValueEdge(), memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Edge property not removed due to not having enough permission!");
}
#endif
remove_prop(&lhs.ValueEdge());
break;
case TypedValue::Type::Null:
@ -2726,6 +2935,13 @@ RemoveLabels::RemoveLabelsCursor::RemoveLabelsCursor(const RemoveLabels &self, u
bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &context) {
SCOPED_PROFILE_OP("RemoveLabels");
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(self_.labels_, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw QueryRuntimeException("Couldn't remove label due to not having enough permission!");
}
#endif
if (!input_cursor_->Pull(frame, context)) return false;
TypedValue &vertex_value = frame[self_.input_symbol_];
@ -2733,6 +2949,15 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont
if (vertex_value.IsNull()) return true;
ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex);
auto &vertex = vertex_value.ValueVertex();
#ifdef MG_ENTERPRISE
if (utils::license::global_license_checker.IsValidLicenseFast() && context.auth_checker &&
!context.auth_checker->Has(vertex, storage::View::OLD,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw QueryRuntimeException("Couldn't remove label due to not having enough permission!");
}
#endif
for (auto label : self_.labels_) {
auto maybe_value = vertex.RemoveLabel(label);
if (maybe_value.HasError()) {
@ -3504,9 +3729,7 @@ bool Merge::MergeCursor::Pull(Frame &frame, ExecutionContext &context) {
if (pull_input_) {
// if we have just now pulled from the input
// and failed to pull from merge_match, we should create
__attribute__((unused)) bool merge_create_pull_result = merge_create_cursor_->Pull(frame, context);
DMG_ASSERT(merge_create_pull_result, "MergeCreate must never fail");
return true;
return merge_create_cursor_->Pull(frame, context);
}
// We have exhausted merge_match_cursor_ after 1 or more successful
// Pulls. Attempt next input_cursor_ pull

View File

@ -24,6 +24,7 @@
#include "mg_procedure.h"
#include "module.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/procedure/cypher_types.hpp"
#include "query/procedure/mg_procedure_helpers.hpp"
#include "query/stream/common.hpp"
@ -31,6 +32,7 @@
#include "storage/v2/view.hpp"
#include "utils/algorithm.hpp"
#include "utils/concepts.hpp"
#include "utils/license.hpp"
#include "utils/logging.hpp"
#include "utils/math.hpp"
#include "utils/memory.hpp"
@ -122,6 +124,10 @@ struct SerializationException : public memgraph::utils::BasicException {
using memgraph::utils::BasicException::BasicException;
};
struct AuthorizationException : public memgraph::utils::BasicException {
using memgraph::utils::BasicException::BasicException;
};
template <typename TFunc, typename TReturn>
concept ReturnsType = std::same_as<std::invoke_result_t<TFunc>, TReturn>;
@ -162,6 +168,9 @@ template <typename TFunc, typename... Args>
} catch (const SerializationException &se) {
spdlog::error("Serialization error during mg API call: {}", se.what());
return mgp_error::MGP_ERROR_SERIALIZATION_ERROR;
} catch (const AuthorizationException &ae) {
spdlog::error("Authorization error during mg API call: {}", ae.what());
return mgp_error::MGP_ERROR_AUTHORIZATION_ERROR;
} catch (const std::bad_alloc &bae) {
spdlog::error("Memory allocation error during mg API call: {}", bae.what());
return mgp_error::MGP_ERROR_UNABLE_TO_ALLOCATE;
@ -1122,6 +1131,7 @@ mgp_error mgp_path_expand(mgp_path *path, mgp_edge *edge) {
// the given edge.
auto *src_vertex = &path->vertices.back();
mgp_vertex *dst_vertex{nullptr};
if (edge->to == *src_vertex) {
dst_vertex = &edge->from;
} else if (edge->from == *src_vertex) {
@ -1640,6 +1650,15 @@ memgraph::storage::PropertyValue ToPropertyValue(const mgp_value &value) {
mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_name, mgp_value *property_value) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw AuthorizationException{"Insufficient permissions for setting a property on vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
}
@ -1664,8 +1683,6 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
}
}
auto &ctx = v->graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
auto *trigger_ctx_collector = ctx->trigger_context_collector;
@ -1685,10 +1702,21 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam
mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!(ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
ctx->auth_checker->Has({label_id}, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) {
throw AuthorizationException{"Insufficient permissions for adding a label to vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
}
const auto label_id = std::visit([label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
const auto result = std::visit([label_id](auto &impl) { return impl.AddLabel(label_id); }, v->impl);
@ -1706,22 +1734,32 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
}
}
auto &ctx = v->graph->ctx;
if (ctx) {
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_LABELS] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterSetVertexLabel(v->getImpl(), label_id);
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterSetVertexLabel(v->getImpl(), label_id);
}
}
});
}
mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
return WrapExceptions([=] {
auto *ctx = v->graph->ctx;
const auto label_id = std::visit([&label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!(ctx->auth_checker->Has(v->getImpl(), v->graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
ctx->auth_checker->Has({label_id}, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) {
throw AuthorizationException{"Insufficient permissions for removing a label from vertex!"};
}
#endif
if (!MgpVertexIsMutable(*v)) {
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
}
const auto label_id = std::visit([&label](auto *impl) { return impl->NameToLabel(label.name); }, v->graph->impl);
const auto result = std::visit([label_id](auto &impl) { return impl.RemoveLabel(label_id); }, v->impl);
if (result.HasError()) {
@ -1738,12 +1776,12 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
}
}
auto &ctx = v->graph->ctx;
if (ctx) {
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_LABELS] += 1;
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->getImpl(), label_id);
if (ctx->trigger_context_collector) {
ctx->trigger_context_collector->RegisterRemovedVertexLabel(v->getImpl(), label_id);
}
}
});
}
@ -1899,6 +1937,33 @@ mgp_error mgp_vertex_iter_properties(mgp_vertex *v, mgp_memory *memory, mgp_prop
void mgp_edges_iterator_destroy(mgp_edges_iterator *it) { DeleteRawMgpObject(it); }
#ifdef MG_ENTERPRISE
namespace {
void NextPermittedEdge(mgp_edges_iterator &it, const bool for_in) {
if (const auto *ctx = it.source_vertex.graph->ctx; !ctx || !ctx->auth_checker) return;
auto &impl_it = for_in ? it.in_it : it.out_it;
const auto end = for_in ? it.in->end() : it.out->end();
if (impl_it) {
const auto *auth_checker = it.source_vertex.graph->ctx->auth_checker.get();
const auto view = it.source_vertex.graph->view;
while (*impl_it != end) {
if (auth_checker->Has(**impl_it, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
const auto &check_vertex =
it.source_vertex.getImpl() == (*impl_it)->From() ? (*impl_it)->To() : (*impl_it)->From();
if (auth_checker->Has(check_vertex, view, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
break;
}
}
++*impl_it;
}
}
};
} // namespace
#endif
mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
return WrapExceptions(
[v, memory] {
@ -1922,6 +1987,12 @@ mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_
}
it->in.emplace(std::move(*maybe_edges));
it->in_it.emplace(it->in->begin());
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermittedEdge(*it, true);
}
#endif
if (*it->in_it != it->in->end()) {
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
@ -1967,6 +2038,13 @@ mgp_error mgp_vertex_iter_out_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges
it->out.emplace(std::move(*maybe_edges));
it->out_it.emplace(it->out->begin());
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermittedEdge(*it, false);
}
#endif
if (*it->out_it != it->out->end()) {
std::visit(memgraph::utils::Overloaded{
[&](memgraph::query::DbAccessor *) {
@ -2007,14 +2085,25 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
return WrapExceptions(
[it] {
MG_ASSERT(it->in || it->out);
auto next = [&](auto *impl_it, const auto &end) -> mgp_edge * {
auto next = [it](const bool for_in) -> mgp_edge * {
auto &impl_it = for_in ? it->in_it : it->out_it;
const auto end = for_in ? it->in->end() : it->out->end();
if (*impl_it == end) {
MG_ASSERT(!it->current_e,
"Iteration is already done, so it->current_e "
"should have been set to std::nullopt");
return nullptr;
}
if (++(*impl_it) == end) {
++*impl_it;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermittedEdge(*it, for_in);
}
#endif
if (*impl_it == end) {
it->current_e = std::nullopt;
return nullptr;
}
@ -2034,9 +2123,12 @@ mgp_error mgp_edges_iterator_next(mgp_edges_iterator *it, mgp_edge **result) {
return &*it->current_e;
};
if (it->in_it) {
return next(&*it->in_it, it->in->end());
auto *result = next(true);
if (result != nullptr) {
return result;
}
}
return next(&*it->out_it, it->out->end());
return next(false);
},
result);
}
@ -2108,6 +2200,15 @@ mgp_error mgp_edge_get_property(mgp_edge *e, const char *name, mgp_memory *memor
mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, mgp_value *property_value) {
return WrapExceptions([=] {
auto *ctx = e->from.graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(e->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
throw AuthorizationException{"Insufficient permissions for setting a property on edge!"};
}
#endif
if (!MgpEdgeIsMutable(*e)) {
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
}
@ -2130,8 +2231,6 @@ mgp_error mgp_edge_set_property(struct mgp_edge *e, const char *property_name, m
}
}
auto &ctx = e->from.graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::UPDATED_PROPERTIES] += 1;
auto *trigger_ctx_collector = e->from.graph->ctx->trigger_context_collector;
@ -2164,7 +2263,8 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti
throw DeletedObjectException{"Cannot get the properties of a deleted edge!"};
case memgraph::storage::Error::NONEXISTENT_OBJECT:
LOG_FATAL(
"Query modules shouldn't have access to nonexistent objects when getting the properties of an edge.");
"Query modules shouldn't have access to nonexistent objects when getting the properties of an "
"edge.");
case memgraph::storage::Error::PROPERTIES_DISABLED:
case memgraph::storage::Error::VERTEX_HAS_EDGES:
case memgraph::storage::Error::SERIALIZATION_ERROR:
@ -2208,7 +2308,17 @@ mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) {
mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, mgp_vertex **result) {
return WrapExceptions(
[=] {
[=]() -> mgp_vertex * {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && graph->ctx &&
graph->ctx->auth_checker &&
!graph->ctx->auth_checker->HasGlobalPrivilegeOnVertices(
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for creating vertices!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
}
@ -2228,6 +2338,16 @@ mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, m
mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return WrapExceptions([=] {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(vertex->getImpl(), graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for deleting a vertex!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
@ -2260,8 +2380,6 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return;
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_NODES] += 1;
if (ctx->trigger_context_collector) {
@ -2272,6 +2390,15 @@ mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) {
return WrapExceptions([=] {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(vertex->getImpl(), graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for deleting a vertex!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
}
@ -2302,8 +2429,6 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
return;
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_NODES] += 1;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_EDGES] +=
static_cast<int64_t>((*result)->second.size());
@ -2326,7 +2451,16 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve
mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *to, mgp_edge_type type,
mgp_memory *memory, mgp_edge **result) {
return WrapExceptions(
[=] {
[=]() -> mgp_edge * {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
const auto edge_id =
std::visit([type](auto *impl) { return impl->NameToEdgeType(type.name); }, from->graph->impl);
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(edge_id, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for creating edges!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"};
}
@ -2357,7 +2491,6 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
throw SerializationException{"Cannot serialize creating an edge."};
}
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::CREATED_EDGES] += 1;
@ -2382,6 +2515,13 @@ mgp_error mgp_graph_create_edge(mgp_graph *graph, mgp_vertex *from, mgp_vertex *
mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
return WrapExceptions([=] {
auto *ctx = graph->ctx;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast() && ctx && ctx->auth_checker &&
!ctx->auth_checker->Has(edge->impl, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
throw AuthorizationException{"Insufficient permissions for deleting an edge!"};
}
#endif
if (!MgpGraphIsMutable(*graph)) {
throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
}
@ -2403,7 +2543,6 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
if (!*result) {
return;
}
auto &ctx = graph->ctx;
ctx->execution_stats[memgraph::query::ExecutionStats::Key::DELETED_EDGES] += 1;
if (ctx->trigger_context_collector) {
@ -2412,6 +2551,50 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
});
}
#ifdef MG_ENTERPRISE
namespace {
void NextPermitted(mgp_vertices_iterator &it) {
const auto *ctx = it.graph->ctx;
if (!ctx || !ctx->auth_checker) {
return;
}
while (it.current_it != it.vertices.end()) {
if (ctx->auth_checker->Has(*it.current_it, it.graph->view,
memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
break;
}
++it.current_it;
}
};
} // namespace
#endif
/// @throw anything VerticesIterable may throw
mgp_vertices_iterator::mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
: memory(memory),
graph(graph),
vertices(std::visit([graph](auto *impl) { return impl->Vertices(graph->view); }, graph->impl)),
current_it(vertices.begin()) {
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermitted(*this);
}
#endif
if (current_it != vertices.end()) {
std::visit(
memgraph::utils::Overloaded{
[this, graph, memory](memgraph::query::DbAccessor *) { current_v.emplace(*current_it, graph, memory); },
[this, graph, memory](memgraph::query::SubgraphDbAccessor *impl) {
current_v.emplace(memgraph::query::SubgraphVertexAccessor(*current_it, impl->getGraph()), graph, memory);
}},
graph->impl);
}
}
void mgp_vertices_iterator_destroy(mgp_vertices_iterator *it) { DeleteRawMgpObject(it); }
mgp_error mgp_graph_iter_vertices(mgp_graph *graph, mgp_memory *memory, mgp_vertices_iterator **result) {
@ -2442,10 +2625,18 @@ mgp_error mgp_vertices_iterator_next(mgp_vertices_iterator *it, mgp_vertex **res
"should have been set to std::nullopt");
return nullptr;
}
if (++it->current_it == it->vertices.end()) {
++it->current_it;
#ifdef MG_ENTERPRISE
if (memgraph::utils::license::global_license_checker.IsValidLicenseFast()) {
NextPermitted(*it);
}
#endif
if (it->current_it == it->vertices.end()) {
it->current_v = std::nullopt;
return nullptr;
}
memgraph::utils::OnScopeExit clean_up([it] { it->current_v = std::nullopt; });
std::visit(memgraph::utils::Overloaded{[it](memgraph::query::DbAccessor *) {
it->current_v.emplace(*it->current_it, it->graph,

View File

@ -714,22 +714,7 @@ struct mgp_vertices_iterator {
using allocator_type = memgraph::utils::Allocator<mgp_vertices_iterator>;
/// @throw anything VerticesIterable may throw
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory)
: memory(memory),
graph(graph),
vertices(std::visit([graph](auto *impl) { return impl->Vertices(graph->view); }, graph->impl)),
current_it(vertices.begin()) {
if (current_it != vertices.end()) {
std::visit(
memgraph::utils::Overloaded{
[this, graph, memory](memgraph::query::DbAccessor *) { current_v.emplace(*current_it, graph, memory); },
[this, graph, memory](memgraph::query::SubgraphDbAccessor *impl) {
current_v.emplace(memgraph::query::SubgraphVertexAccessor(*current_it, impl->getGraph()), graph,
memory);
}},
graph->impl);
}
}
mgp_vertices_iterator(mgp_graph *graph, memgraph::utils::MemoryResource *memory);
memgraph::utils::MemoryResource *GetMemoryResource() const { return memory; }

View File

@ -51,6 +51,7 @@ PyObject *gMgpKeyAlreadyExistsError{nullptr}; // NOLINT(cppcoreguidelines-avo
PyObject *gMgpImmutableObjectError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
PyObject *gMgpValueConversionError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
PyObject *gMgpSerializationError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
PyObject *gMgpAuthorizationError{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Returns true if an exception is raised
bool RaiseExceptionFromErrorCode(const mgp_error error) {
@ -101,6 +102,10 @@ bool RaiseExceptionFromErrorCode(const mgp_error error) {
PyErr_SetString(gMgpSerializationError, "Operation cannot be serialized.");
return true;
}
case mgp_error::MGP_ERROR_AUTHORIZATION_ERROR: {
PyErr_SetString(gMgpAuthorizationError, "Authorization Error. Permission Denied.");
return true;
}
}
}
@ -2192,6 +2197,7 @@ PyObject *PyInitMgpModule() {
PyMgpError{"_mgp.ImmutableObjectError", gMgpImmutableObjectError, PyExc_RuntimeError, nullptr},
PyMgpError{"_mgp.ValueConversionError", gMgpValueConversionError, PyExc_RuntimeError, nullptr},
PyMgpError{"_mgp.SerializationError", gMgpSerializationError, PyExc_RuntimeError, nullptr},
PyMgpError{"_mgp.AuthorizationError", gMgpAuthorizationError, PyExc_RuntimeError, nullptr},
};
Py_INCREF(Py_None);

View File

@ -95,7 +95,7 @@ static memgraph::query::CypherQuery *AddIndexedMatches(int num_matches, const st
static auto CreateIndexedVertices(int index_count, int vertex_count, memgraph::storage::Storage *db) {
auto label = db->NameToLabel("label");
auto prop = db->NameToProperty("prop");
db->CreateIndex(label, prop);
[[maybe_unused]] auto _ = db->CreateIndex(label, prop);
auto dba = db->Access();
for (int vi = 0; vi < vertex_count; ++vi) {
for (int index = 0; index < index_count; ++index) {

View File

@ -29,6 +29,7 @@ function(copy_e2e_cpp_files TARGET_PREFIX FILE_NAME)
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME})
endfunction()
add_subdirectory(fine_grained_access)
add_subdirectory(server)
add_subdirectory(replication)
add_subdirectory(memory)
@ -41,6 +42,7 @@ add_subdirectory(configuration)
add_subdirectory(magic_functions)
add_subdirectory(module_file_manager)
add_subdirectory(monitoring_server)
add_subdirectory(lba_procedures)
copy_e2e_python_files(pytest_runner pytest_runner.sh "")
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/memgraph-selfsigned.crt DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@ -0,0 +1,8 @@
function(copy_fine_grained_access_e2e_python_files FILE_NAME)
copy_e2e_python_files(fine_grained_access ${FILE_NAME})
endfunction()
copy_fine_grained_access_e2e_python_files(common.py)
copy_fine_grained_access_e2e_python_files(create_delete_filtering_tests.py)
copy_fine_grained_access_e2e_python_files(edge_type_filtering_tests.py)
copy_fine_grained_access_e2e_python_files(path_filtering_tests.py)

View File

@ -0,0 +1,31 @@
# Copyright 2021 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 mgclient
def reset_and_prepare(admin_cursor):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "MATCH(n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "CREATE (n:test_delete {name: 'test1'});")
execute_and_fetch_all(admin_cursor, "CREATE (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2);")
def execute_and_fetch_all(cursor, query):
cursor.execute(query)
return cursor.fetchall()
def connect(**kwargs):
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
return connection

View File

@ -0,0 +1,517 @@
# 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 pytest
import sys
from mgclient import DatabaseError
import common
def test_create_node_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
assert len(results) == 1
def test_create_node_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
def test_create_node_specific_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
assert len(results) == 1
def test_create_node_specific_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "CREATE (n:label1) RETURN n;")
def test_delete_node_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) RETURN n;")
assert len(results) == 0
def test_delete_node_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n")
def test_delete_node_specific_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :test_delete TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n;")
results = common.execute_and_fetch_all(admin_connection.cursor(), "MATCH (n:test_delete) RETURN n;")
assert len(results) == 0
def test_delete_node_specific_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :test_delete TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n:test_delete) DELETE n;")
def test_create_edge_all_labels_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
assert len(results) == 1
def test_create_edge_all_labels_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_all_labels_denied_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_all_labels_granted_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_all_labels_granted_specific_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_first_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_create_edge_second_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"CREATE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_delete_edge_all_labels_denied_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_all_labels_granted_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_all_labels_granted_specific_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON EDGE_TYPES :edge_type_delete TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_first_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :test_delete_1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete_2 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type_delete TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_edge_second_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :test_delete_2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete_1 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type_delete TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n:test_delete_1)-[r:edge_type_delete]->(m:test_delete_2) DELETE r",
)
def test_delete_node_with_edge_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON LABELS :test_delete_1 TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n) DETACH DELETE n;")
def test_delete_node_with_edge_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON LABELS :test_delete_1 TO user;",
)
common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n) DETACH DELETE n;")
results = common.execute_and_fetch_all(admin_connection.cursor(), "MATCH (n:test_delete_1) RETURN n;")
assert len(results) == 0
def test_merge_node_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
assert len(results) == 1
def test_merge_node_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
def test_merge_node_specific_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
assert len(results) == 1
def test_merge_node_specific_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connnection.cursor(), "MERGE (n:label1) RETURN n;")
def test_merge_edge_all_labels_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
assert len(results) == 1
def test_merge_edge_all_labels_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_all_labels_denied_all_edge_types_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_all_labels_granted_all_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON EDGE_TYPES * TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_all_labels_granted_specific_edge_types_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT UPDATE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_first_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_merge_edge_second_node_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :label2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :label1 TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(),
"GRANT CREATE_DELETE ON EDGE_TYPES :edge_type TO user;",
)
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(
user_connnection.cursor(),
"MERGE (n:label1)-[r:edge_type]->(m:label2) RETURN n,r,m;",
)
def test_set_label_when_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :update_label_2 TO user;")
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) SET p:update_label_2;")
def test_set_label_when_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :update_label_2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) SET p:update_label_2;")
def test_remove_label_when_label_granted():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :test_delete TO user;")
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;")
def test_remove_label_when_label_denied():
admin_connection = common.connect(username="admin", password="test")
user_connection = common.connect(username="user", password="test")
common.reset_and_prepare(admin_connection.cursor())
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT UPDATE ON LABELS :update_label_2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete TO user;")
with pytest.raises(DatabaseError):
common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;")
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,94 @@
import common
import sys
import pytest
def test_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 3
def test_deny_all_edge_types_and_all_labels():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
def test_revoke_all_edge_types_and_all_labels():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
def test_deny_edge_type():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edgeType2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edgeType1 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 2
def test_denied_node_label():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :label1,:label3 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edgeType1, :edgeType2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label2 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 2
def test_denied_one_of_node_label():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :label1,:label2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edgeType1, :edgeType2 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label3 TO user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 1
def test_revoke_all_labels():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
def test_revoke_all_edge_types():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH (n)-[r]->(m) RETURN n,r,m;")
assert len(results) == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,717 @@
import common
import sys
import pytest
def test_weighted_shortest_path_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length,nodes(p);",
)
expected_path = [0, 1, 3, 4, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 3],
[0, 1, 3, 4],
[0, 1, 3, 4, 5],
[1, 2],
[1, 3],
[1, 3, 4],
[1, 3, 4, 5],
[2, 1],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 20
assert all(node.id in expected_path for node in path_result[0][1])
def test_weighted_shortest_path_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN p;"
)
assert len(results) == 0
def test_weighted_shortest_path_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_weighted_shortest_path_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_weighted_shortest_path_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
expected_path = [0, 2, 3, 4, 5]
expected_all_paths = [
[0, 2],
[0, 2, 3],
[0, 2, 3, 4],
[0, 2, 3, 4, 5],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 11
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 30
assert all(node.id in expected_path for node in path_result[0][1])
def test_weighted_shortest_path_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *wShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *wShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 1, 2, 3, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 2, 4],
[0, 1, 2, 4, 3],
[0, 1, 2, 4, 5],
[1, 2, 4, 3],
[1, 2],
[1, 2, 4],
[1, 2, 4, 5],
[2, 1],
[2, 4, 3],
[2, 4],
[2, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 25
assert all(node.id in expected_path for node in path_result[0][1])
def test_dfs_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_paths = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH path=(n:label0)-[* 1..3]->(m:label4) RETURN extract( node in nodes(path) | node.id);",
)
expected_paths = [[0, 1, 3, 5], [0, 2, 3, 5], [0, 2, 4, 5]]
assert len(source_destination_paths) == 3
assert all(path[0] in expected_paths for path in source_destination_paths)
def test_dfs_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH p=(n)-[*]->(m) RETURN p;")
assert len(total_paths_results) == 0
def test_dfs_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[*]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_dfs_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[*]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_dfs_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_paths = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[* 1..3]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_paths = [[0, 2, 3, 5], [0, 2, 4, 5]]
assert len(source_destination_paths) == 2
assert all(path[0] in expected_paths for path in source_destination_paths)
def test_dfs_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r * 1..3]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_sts_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 1, 3, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_sts_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH (n), (m) WITH n, m MATCH p=(n)-[r *BFS]->(m) RETURN p;"
)
assert len(total_paths_results) == 0
def test_bfs_sts_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_sts_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_sts_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_sts_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH (n), (m) WITH n, m MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_single_source_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 3, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_single_source_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(user_connnection.cursor(), "MATCH p=(n)-[r *BFS]->(m) RETURN p;")
assert len(total_paths_results) == 0
def test_bfs_single_source_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_single_source_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN p;"
)
assert len(source_destination_path) == 0
def test_bfs_single_source_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 3, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_bfs_single_source_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
source_destination_path = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *BFS]->(m:label4) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 2, 4, 5]
assert len(source_destination_path) == 1
assert source_destination_path[0][0] == expected_path
def test_all_shortest_paths_when_all_edge_types_all_labels_granted():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length,nodes(p);",
)
expected_path = [0, 1, 3, 4, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 3],
[0, 1, 3, 4],
[0, 1, 3, 4, 5],
[1, 2],
[1, 3],
[1, 3, 4],
[1, 3, 4, 5],
[2, 1],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 20
assert all(node.id in expected_path for node in path_result[0][1])
def test_all_shortest_paths_when_all_edge_types_all_labels_denied():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES * TO user;")
results = common.execute_and_fetch_all(
user_connnection.cursor(), "MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN p;"
)
assert len(results) == 0
def test_all_shortest_paths_when_denied_start():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label0 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_all_shortest_paths_when_denied_destination():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label4 TO user;")
path_length_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;",
)
assert len(path_length_result) == 0
def test_all_shortest_paths_when_denied_label_1():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON LABELS :label1 TO user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;")
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
expected_path = [0, 2, 3, 4, 5]
expected_all_paths = [
[0, 2],
[0, 2, 3],
[0, 2, 3, 4],
[0, 2, 3, 4, 5],
[2, 3],
[2, 3, 4],
[2, 3, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 11
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 30
assert all(node.id in expected_path for node in path_result[0][1])
def test_all_shortest_paths_when_denied_edge_type_3():
admin_connection = common.connect(username="admin", password="test")
user_connnection = common.connect(username="user", password="test")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;")
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;")
common.execute_and_fetch_all(
admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;"
)
common.execute_and_fetch_all(admin_connection.cursor(), "GRANT NOTHING ON EDGE_TYPES :edge_type_3 TO user;")
path_result = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);",
)
total_paths_results = common.execute_and_fetch_all(
user_connnection.cursor(),
"MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);",
)
expected_path = [0, 1, 2, 3, 5]
expected_all_paths = [
[0, 1],
[0, 1, 2],
[0, 1, 2, 4],
[0, 1, 2, 4, 3],
[0, 1, 2, 4, 5],
[1, 2, 4, 3],
[1, 2],
[1, 2, 4],
[1, 2, 4, 5],
[2, 1],
[2, 4, 3],
[2, 4],
[2, 4, 5],
[3, 4],
[3, 4, 5],
[4, 3],
[4, 5],
]
assert len(total_paths_results) == 16
assert all(path[0] in expected_all_paths for path in total_paths_results)
assert path_result[0][0] == 25
assert all(node.id in expected_path for node in path_result[0][1])
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,74 @@
bolt_port: &bolt_port "7687"
create_delete_filtering_cluster: &create_delete_filtering_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
]
edge_type_filtering_cluster: &edge_type_filtering_cluster
cluster:
main:
args: ["--bolt-port", *bolt_port, "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
"GRANT CREATE_DELETE ON LABELS * TO admin;",
"GRANT CREATE_DELETE ON EDGE_TYPES * TO admin;",
"MERGE (l1:label1 {name: 'test1'});",
"MERGE (l2:label2 {name: 'test2'});",
"MATCH (l1:label1),(l2:label2) WHERE l1.name = 'test1' AND l2.name = 'test2' CREATE (l1)-[r:edgeType1]->(l2);",
"MERGE (l3:label3 {name: 'test3'});",
"MATCH (l1:label1),(l3:label3) WHERE l1.name = 'test1' AND l3.name = 'test3' CREATE (l1)-[r:edgeType2]->(l3);",
"MERGE (mix:label3:label1 {name: 'test4'});",
"MATCH (l1:label1),(mix:label3) WHERE l1.name = 'test1' AND mix.name = 'test4' CREATE (l1)-[r:edgeType2]->(mix);",
]
validation_queries: []
path_filtering_cluster: &path_filtering_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
"MERGE (a:label0 {id: 0}) MERGE (b:label1 {id: 1}) CREATE (a)-[:edge_type_1 {weight: 6}]->(b);",
"MERGE (a:label0 {id: 0}) MERGE (b:label2 {id: 2}) CREATE (a)-[:edge_type_1 {weight: 14}]->(b);",
"MERGE (a:label1 {id: 1}) MERGE (b:label2 {id: 2}) CREATE (a)-[:edge_type_2 {weight: 1}]->(b);",
"MERGE (a:label2 {id: 2}) MERGE (b:label3 {id: 4}) CREATE (a)-[:edge_type_2 {weight: 10}]->(b);",
"MERGE (a:label1 {id: 1}) MERGE (b:label3 {id: 3}) CREATE (a)-[:edge_type_3 {weight: 5}]->(b);",
"MERGE (a:label2 {id: 2}) MERGE (b:label3 {id: 3}) CREATE (a)-[:edge_type_3 {weight: 7}]->(b);",
"MERGE (a:label3 {id: 3}) MERGE (b:label3 {id: 4}) CREATE (a)-[:edge_type_4 {weight: 1}]->(b);",
"MERGE (a:label3 {id: 4}) MERGE (b:label3 {id: 3}) CREATE (a)-[:edge_type_4 {weight: 1}]->(b);",
"MERGE (a:label3 {id: 3}) MERGE (b:label4 {id: 5}) CREATE (a)-[:edge_type_4 {weight: 14}]->(b);",
"MERGE (a:label3 {id: 4}) MERGE (b:label4 {id: 5}) CREATE (a)-[:edge_type_4 {weight: 8}]->(b);",
]
workloads:
- name: "Create delete filtering"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/create_delete_filtering_tests.py"]
<<: *create_delete_filtering_cluster
- name: "EdgeType filtering"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/edge_type_filtering_tests.py"]
<<: *edge_type_filtering_cluster
- name: "Path filtering"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/path_filtering_tests.py"]
<<: *path_filtering_cluster

View File

@ -0,0 +1,13 @@
function(copy_lba_procedures_e2e_python_files FILE_NAME)
copy_e2e_python_files(lba_procedures ${FILE_NAME})
endfunction()
copy_lba_procedures_e2e_python_files(common.py)
copy_lba_procedures_e2e_python_files(show_privileges.py)
copy_lba_procedures_e2e_python_files(read_query_modules.py)
copy_lba_procedures_e2e_python_files(update_query_modules.py)
copy_lba_procedures_e2e_python_files(create_delete_query_modules.py)
copy_lba_procedures_e2e_python_files(read_permission_queries.py)
copy_lba_procedures_e2e_python_files(update_permission_queries.py)
add_subdirectory(procedures)

View File

@ -0,0 +1,69 @@
# Copyright 2021 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 mgclient
import typing
def execute_and_fetch_all(cursor: mgclient.Cursor, query: str, params: dict = {}) -> typing.List[tuple]:
cursor.execute(query, params)
return cursor.fetchall()
def connect(**kwargs) -> mgclient.Connection:
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
return connection
def reset_permissions(admin_cursor: mgclient.Cursor, create_index: bool = False):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "MATCH(n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "DROP INDEX ON :read_label(prop);")
execute_and_fetch_all(admin_cursor, "DROP INDEX ON :read_label;")
execute_and_fetch_all(admin_cursor, "CREATE (n:read_label {prop: 5});")
execute_and_fetch_all(
admin_cursor, "CREATE (n:read_label_1 {prop: 5})-[r:read_edge_type]->(m:read_label_2 {prop: 5});"
)
if create_index:
execute_and_fetch_all(admin_cursor, "CREATE INDEX ON :read_label;")
execute_and_fetch_all(admin_cursor, "CREATE INDEX ON :read_label(prop);")
def reset_update_permissions(admin_cursor: mgclient.Cursor):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "MATCH (n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "CREATE (n:update_label {prop: 1});")
execute_and_fetch_all(
admin_cursor,
"CREATE (n:update_label_1)-[r:update_edge_type {prop: 1}]->(m:update_label_2);",
)
def reset_create_delete_permissions(admin_cursor: mgclient.Cursor):
execute_and_fetch_all(admin_cursor, "REVOKE LABELS * FROM user;")
execute_and_fetch_all(admin_cursor, "REVOKE EDGE_TYPES * FROM user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES * TO user;")
execute_and_fetch_all(admin_cursor, "MATCH (n) DETACH DELETE n;")
execute_and_fetch_all(admin_cursor, "CREATE (n:create_delete_label);")
execute_and_fetch_all(
admin_cursor,
"CREATE (n:create_delete_label_1)-[r:create_delete_edge_type]->(m:create_delete_label_2);",
)

View File

@ -0,0 +1,299 @@
# 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 pytest
import sys
from common import (
connect,
execute_and_fetch_all,
mgclient,
reset_create_delete_permissions,
)
AUTHORIZATION_ERROR_IDENTIFIER = "AuthorizationError"
create_vertex_query = "CALL create_delete.create_vertex() YIELD created_node RETURN labels(created_node);"
remove_label_vertex_query = "CALL create_delete.remove_label('create_delete_label') YIELD node RETURN labels(node);"
set_label_vertex_query = "CALL create_delete.set_label('new_create_delete_label') YIELD node RETURN labels(node);"
create_edge_query = "MATCH (n:create_delete_label_1), (m:create_delete_label_2) CALL create_delete.create_edge(n, m) YIELD nr_of_edges RETURN nr_of_edges;"
delete_edge_query = "CALL create_delete.delete_edge() YIELD * RETURN *;"
def test_can_not_create_vertex_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_create_vertex_when_given_global_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, create_vertex_query)
len(result[0][0]) == 1
def test_can_not_create_vertex_when_given_global_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_not_create_vertex_when_given_global_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_add_vertex_label_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON LABELS :new_create_delete_label, UPDATE ON LABELS :create_delete_label TO user;",
)
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_label_vertex_query)
assert "create_delete_label" in result[0][0]
assert "new_create_delete_label" in result[0][0]
def test_can_not_add_vertex_label_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor, "GRANT UPDATE ON LABELS :new_create_delete_label, :create_delete_label TO user;"
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, set_label_vertex_query)
def test_can_not_add_vertex_label_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor, "GRANT READ ON LABELS :new_create_delete_label, UPDATE ON LABELS :create_delete_label TO user;"
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, set_label_vertex_query)
def test_can_remove_vertex_label_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, remove_label_vertex_query)
assert result[0][0] != ":create_delete_label"
def test_can_remove_vertex_label_when_given_global_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, remove_label_vertex_query)
assert result[0][0] != ":create_delete_label"
def test_can_not_remove_vertex_label_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_global_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_global_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_create_edge_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_not_create_edge_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :new_create_delete_edge_type TO user")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_not_create_edge_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES :new_create_delete_edge_type TO user")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_create_edge_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON EDGE_TYPES :new_create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
no_of_edges = execute_and_fetch_all(test_cursor, create_edge_query)
assert no_of_edges[0][0] == 2
def test_can_not_delete_edge_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_not_delete_edge_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT READ ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_not_delete_edge_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT UPDATE ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_delete_edge_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
no_of_edges = execute_and_fetch_all(test_cursor, delete_edge_query)
assert no_of_edges[0][0] == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,3 @@
copy_lba_procedures_e2e_python_files(read.py)
copy_lba_procedures_e2e_python_files(update.py)
copy_lba_procedures_e2e_python_files(create_delete.py)

View File

@ -0,0 +1,63 @@
# Copyright 2021 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 mgp
@mgp.write_proc
def create_vertex(ctx: mgp.ProcCtx) -> mgp.Record(created_node=mgp.Vertex):
vertex = ctx.graph.create_vertex()
return mgp.Record(created_node=vertex)
@mgp.write_proc
def remove_label(ctx: mgp.ProcCtx, label: str) -> mgp.Record(node=mgp.Vertex):
for vertex in ctx.graph.vertices:
if "create_delete_label" in vertex.labels:
break
vertex.remove_label(label)
return mgp.Record(node=vertex)
@mgp.write_proc
def set_label(ctx: mgp.ProcCtx, new_label: str) -> mgp.Record(node=mgp.Vertex):
for vertex in ctx.graph.vertices:
if "create_delete_label" in vertex.labels:
break
vertex.add_label(new_label)
return mgp.Record(node=vertex)
@mgp.write_proc
def create_edge(ctx: mgp.ProcCtx, v1: mgp.Vertex, v2: mgp.Vertex) -> mgp.Record(nr_of_edges=int):
ctx.graph.create_edge(v1, v2, mgp.EdgeType("new_create_delete_edge_type"))
count = 0
for vertex in ctx.graph.vertices:
for _ in vertex.out_edges:
count += 1
return mgp.Record(nr_of_edges=count)
@mgp.write_proc
def delete_edge(ctx: mgp.ProcCtx) -> mgp.Record(edge_count=int):
count = 0
for vertex in ctx.graph.vertices:
for edge in vertex.out_edges:
if edge.type.name == "create_delete_edge_type":
ctx.graph.delete_edge(edge)
else:
count += 1
return mgp.Record(edge_count=count)

View File

@ -0,0 +1,27 @@
# Copyright 2021 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 mgp
@mgp.read_proc
def number_of_visible_nodes(ctx: mgp.ProcCtx) -> mgp.Record(nr_of_nodes=int):
return mgp.Record(nr_of_nodes=len(mgp.Vertices(ctx.graph._graph)))
@mgp.read_proc
def number_of_visible_edges(ctx: mgp.ProcCtx) -> mgp.Record(nr_of_edges=int):
count = 0
for vertex in ctx.graph.vertices:
for _ in vertex.out_edges:
count += 1
return mgp.Record(nr_of_edges=count)

View File

@ -0,0 +1,21 @@
# Copyright 2021 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 mgp
@mgp.write_proc
def set_property(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record():
try:
object.properties.set("prop", 2)
except mgp.AuthorizationError:
pass
return mgp.Record()

View File

@ -0,0 +1,162 @@
# 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 pytest
import sys
from typing import List
from common import connect, execute_and_fetch_all, reset_permissions
match_query = "MATCH (n) RETURN n;"
match_by_id_query = "MATCH (n) WHERE ID(n) >= 0 RETURN n;"
match_by_label_query = "MATCH (n) RETURN n;"
match_by_label_property_range_query = "MATCH (n) WHERE n.prop < 7 RETURN n;"
match_by_label_property_value_query = "MATCH (n {prop: 5}) RETURN n;"
match_by_label_property_query = "MATCH (n) WHERE n.prop IS NOT NULL RETURN n;"
read_node_without_index_operation_cases = [
["GRANT READ ON LABELS :read_label TO user;"],
["GRANT READ ON LABELS * TO user;"],
["GRANT UPDATE ON LABELS :read_label TO user;"],
["GRANT UPDATE ON LABELS * TO user;"],
["GRANT CREATE_DELETE ON LABELS :read_label TO user;"],
["GRANT CREATE_DELETE ON LABELS * TO user;"],
]
read_node_without_index_operation_cases_expected_size = [1, 3, 1, 3, 1, 3]
read_node_with_index_operation_cases = [
["GRANT READ ON LABELS :read_label TO user;"],
["GRANT READ ON LABELS * TO user;"],
["GRANT UPDATE ON LABELS :read_label TO user;"],
["GRANT UPDATE ON LABELS * TO user;"],
["GRANT CREATE_DELETE ON LABELS :read_label TO user;"],
["GRANT CREATE_DELETE ON LABELS * TO user;"],
]
read_node_with_index_operation_cases_expected_sizes = [1, 3, 1, 3, 1, 3]
not_read_node_without_index_operation_cases = [
[],
["GRANT NOTHING ON LABELS :read_label TO user;"],
["GRANT NOTHING ON LABELS * TO user;"],
[
"GRANT UPDATE ON LABELS :read_label TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
[
"GRANT UPDATE ON LABELS * TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
[
"GRANT CREATE_DELETE ON LABELS :read_label TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
[
"GRANT CREATE_DELETE ON LABELS * TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
]
not_read_node_without_index_operation_cases_expected_sizes = [0, 0, 0, 0, 2, 0, 2]
not_read_node_with_index_operation_cases = [
[],
["GRANT NOTHING ON LABELS :read_label TO user;"],
["GRANT NOTHING ON LABELS * TO user;"],
[
"GRANT UPDATE ON LABELS :read_label TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
[
"GRANT UPDATE ON LABELS * TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
[
"GRANT CREATE_DELETE ON LABELS :read_label TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
[
"GRANT CREATE_DELETE ON LABELS * TO user;",
"GRANT NOTHING ON LABELS :read_label TO user",
],
]
not_read_node_with_index_operation_cases_expexted_sizes = [0, 0, 0, 0, 2, 0, 2]
def get_admin_cursor():
return connect(username="admin", password="test").cursor()
def get_user_cursor():
return connect(username="user", password="test").cursor()
def execute_read_node_assertion(
operation_case: List[str], queries: List[str], create_index: bool, expected_size: int
) -> None:
admin_cursor = get_admin_cursor()
user_cursor = get_user_cursor()
reset_permissions(admin_cursor, create_index)
for operation in operation_case:
execute_and_fetch_all(admin_cursor, operation)
for mq in queries:
results = execute_and_fetch_all(user_cursor, mq)
assert len(results) == expected_size
def test_can_read_node_when_authorized():
match_queries_without_index = [match_query, match_by_id_query]
match_queries_with_index = [
match_by_label_query,
match_by_label_property_query,
match_by_label_property_range_query,
match_by_label_property_value_query,
]
for expected_size, operation_case in zip(
read_node_without_index_operation_cases_expected_size, read_node_without_index_operation_cases
):
execute_read_node_assertion(operation_case, match_queries_without_index, False, expected_size)
for expected_size, operation_case in zip(
read_node_with_index_operation_cases_expected_sizes, read_node_with_index_operation_cases
):
execute_read_node_assertion(operation_case, match_queries_with_index, True, expected_size)
def test_can_not_read_node_when_authorized():
match_queries_without_index = [match_query, match_by_id_query]
match_queries_with_index = [
match_by_label_query,
match_by_label_property_query,
match_by_label_property_range_query,
match_by_label_property_value_query,
]
for expected_size, operation_case in zip(
not_read_node_without_index_operation_cases_expected_sizes, not_read_node_without_index_operation_cases
):
execute_read_node_assertion(operation_case, match_queries_without_index, False, expected_size)
for expected_size, operation_case in zip(
not_read_node_with_index_operation_cases_expexted_sizes, not_read_node_with_index_operation_cases
):
execute_read_node_assertion(operation_case, match_queries_with_index, True, expected_size)
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,225 @@
# 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, reset_permissions
get_number_of_vertices_query = "CALL read.number_of_visible_nodes() YIELD nr_of_nodes RETURN nr_of_nodes;"
get_number_of_edges_query = "CALL read.number_of_visible_edges() YIELD nr_of_edges RETURN nr_of_edges;"
def test_can_read_vertex_through_c_api_when_given_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 1
def test_can_read_vertex_through_c_api_when_given_update_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 1
def test_can_read_vertex_through_c_api_when_given_create_delete_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 1
def test_can_not_read_vertex_through_c_api_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 0
def test_can_not_read_vertex_through_c_api_when_given_deny_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 0
def test_can_read_partial_vertices_through_c_api_when_given_global_read_but_deny_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON LABELS :read_label TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 2
def test_can_read_partial_vertices_through_c_api_when_given_global_update_but_deny_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON LABELS :read_label TO user;")
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 2
def test_can_read_partial_vertices_through_c_api_when_given_global_create_delete_but_deny_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON LABELS :read_label TO user;")
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 2
def test_can_read_edge_through_c_api_when_given_grant_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :read_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 1
def test_can_not_read_edge_through_c_api_when_given_deny_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON EDGE_TYPES :read_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 0
def test_can_read_edge_through_c_api_when_given_grant_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :read_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 1
def test_can_read_edge_through_c_api_when_given_update_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES :read_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 1
def test_can_read_edge_through_c_api_when_given_create_delete_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON EDGE_TYPES :read_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 1
def test_can_not_read_edge_through_c_api_when_given_read_global_but_deny_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON EDGE_TYPES :read_edge_type TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 0
def test_can_not_read_edge_through_c_api_when_given_update_global_but_deny_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON EDGE_TYPES :read_edge_type TO user;")
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 0
def test_can_not_read_edge_through_c_api_when_given_create_delete_global_but_deny_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label_1, :read_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT NOTHING ON EDGE_TYPES :read_edge_type TO user;")
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON EDGE_TYPES * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_edges_query)
assert result[0][0] == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -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", "NOTHING", "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", "NOTHING", "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", "NOTHING", "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", "NOTHING", "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"]))

View File

@ -0,0 +1,149 @@
# 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 mgclient import DatabaseError
from common import connect, execute_and_fetch_all, reset_update_permissions
update_property_query = "MATCH (n:update_label) SET n.prop = 2 RETURN n.prop;"
update_properties_query = "MATCH (n:update_label) SET n = {prop: 2, prop2: 3} RETURN n.prop;"
remove_property_query = "MATCH (n:update_label) REMOVE n.prop RETURN n.prop;"
def test_can_read_node_when_given_update_grant():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
results = execute_and_fetch_all(test_cursor, "MATCH (n:update_label) RETURN n;")
assert len(results) == 1
def test_can_update_node_when_given_update_grant():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
update_property_actual = execute_and_fetch_all(test_cursor, update_property_query)
update_properties_actual = execute_and_fetch_all(test_cursor, update_properties_query)
remove_property_actual = execute_and_fetch_all(test_cursor, remove_property_query)
assert update_property_actual[0][0] == 2
assert update_properties_actual[0][0] == 2
assert remove_property_actual[0][0] is None
def test_can_not_update_node_when_given_deny():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, update_property_query)
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, update_properties_query)
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, remove_property_query)
def test_can_not_update_node_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, update_property_query)
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, update_properties_query)
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, remove_property_query)
def test_can_not_update_node_when_given_read_globally():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, update_property_query)
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, update_properties_query)
with pytest.raises(DatabaseError):
execute_and_fetch_all(test_cursor, remove_property_query)
def test_can_update_node_when_given_update_globally():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
update_property_actual = execute_and_fetch_all(test_cursor, update_property_query)
update_properties_actual = execute_and_fetch_all(test_cursor, update_properties_query)
remove_property_actual = execute_and_fetch_all(test_cursor, remove_property_query)
assert update_property_actual[0][0] == 2
assert update_properties_actual[0][0] == 2
assert remove_property_actual[0][0] is None
def test_can_update_node_when_given_create_delete_globally():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
update_property_actual = execute_and_fetch_all(test_cursor, update_property_query)
update_properties_actual = execute_and_fetch_all(test_cursor, update_properties_query)
remove_property_actual = execute_and_fetch_all(test_cursor, remove_property_query)
assert update_property_actual[0][0] == 2
assert update_properties_actual[0][0] == 2
assert remove_property_actual[0][0] is None
def test_can_update_node_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
update_property_actual = execute_and_fetch_all(test_cursor, update_property_query)
update_properties_actual = execute_and_fetch_all(test_cursor, update_properties_query)
remove_property_actual = execute_and_fetch_all(test_cursor, remove_property_query)
assert update_property_actual[0][0] == 2
assert update_properties_actual[0][0] == 2
assert remove_property_actual[0][0] is None
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,184 @@
# 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 pytest
import sys
from common import (
connect,
execute_and_fetch_all,
reset_update_permissions,
)
set_vertex_property_query = "MATCH (n:update_label) CALL update.set_property(n) YIELD * RETURN n.prop;"
set_edge_property_query = "MATCH (n:update_label_1)-[r:update_edge_type]->(m:update_label_2) CALL update.set_property(r) YIELD * RETURN r.prop;"
def test_can_not_update_vertex_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 1
def test_can_update_vertex_when_given_update_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_update_vertex_when_given_create_delete_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_update_vertex_when_given_update_global_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_update_vertex_when_given_create_delete_global_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_not_update_vertex_when_denied_update_and_granted_global_update_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label TO user;")
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 1
def test_can_not_update_vertex_when_denied_update_and_granted_global_create_delete_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label TO user;")
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 1
def test_can_update_edge_when_given_update_grant_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_1 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES :update_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_edge_property_query)
assert result[0][0] == 2
def test_can_not_update_edge_when_given_read_grant_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_1 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :update_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_edge_property_query)
assert result[0][0] == 1
def test_can_update_edge_when_given_create_delete_grant_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_1 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON EDGE_TYPES :update_edge_type TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_edge_property_query)
assert result[0][0] == 2
def test_can_not_update_edge_when_denied_update_edge_type_but_granted_global_update_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_1 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :update_edge_type TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_edge_property_query)
assert result[0][0] == 1
def test_can_not_update_edge_when_denied_update_edge_type_but_granted_global_create_delete_on_edge_type():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_1 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label_2 TO user;")
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :update_edge_type TO user;")
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_edge_property_query)
assert result[0][0] == 1
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

@ -0,0 +1,133 @@
read_query_modules_cluster: &read_query_modules_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "lba-e2e.log"
setup_queries:
- "CREATE USER admin IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO admin"
- "CREATE USER user IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO user"
validation_queries: []
update_query_modules_cluster: &update_query_modules_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "lba-e2e.log"
setup_queries:
- "CREATE USER admin IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO admin"
- "CREATE USER user IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO user"
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;"
- "Grant Nothing On Labels :Label2 to Josip;"
- "Grant Update On Labels :Label3 to Josip;"
- "Grant Read On Labels :Label4 to Josip;"
- "Grant Create_Delete On Labels :Label5 to Josip;"
- "Grant Update On Labels :Label6 to Josip;"
- "Grant Create_Delete On Labels :Label7 to Josip;"
- "Grant Nothing On Labels :Label7 to Josip;"
- "Create User Boris;"
- "Grant Auth to Boris;"
- "Grant Read On Labels :Label1 to Boris;"
- "Grant Nothing On Labels :Label2 to Boris;"
- "Grant Update On Labels :Label3 to Boris;"
- "Grant Read On Labels :Label4 to Boris;"
- "Grant Create_Delete On Labels :Label5 to Boris;"
- "Grant Update On Labels :Label6 to Boris;"
- "Grant Create_Delete On Labels :Label7 to Boris;"
- "Grant Nothing On Labels :Label7 to Boris;"
- "Create User Niko;"
- "Grant Auth to Niko;"
- "Grant Create_Delete On Labels * to Niko"
- "Grant Read On Labels * to Niko"
- "Create User Bruno;"
- "Grant Auth to Bruno;"
- "Grant Update On Labels * to Bruno"
validation_queries: []
read_permission_queries: &read_permission_queries
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "lba-e2e.log"
setup_queries:
- "CREATE USER admin IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO admin"
- "CREATE USER user IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO user"
validation_queries: []
create_delete_query_modules_cluster: &create_delete_query_modules_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "lba-e2e.log"
setup_queries:
- "CREATE USER admin IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO admin;"
- "CREATE USER user IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO user;"
validation_queries: []
update_permission_queries_cluster: &update_permission_queries_cluster
cluster:
main:
args: ["--bolt-port", "7687", "--log-level=TRACE"]
log_file: "lba-e2e.log"
setup_queries:
- "CREATE USER admin IDENTIFIED BY 'test';"
- "GRANT ALL PRIVILEGES TO admin;"
- "CREATE USER user IDENTIFIED BY 'test'"
- "GRANT ALL PRIVILEGES TO user;"
validation_queries: []
workloads:
- name: "read-query-modules"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/lba_procedures/procedures/"
args: ["lba_procedures/read_query_modules.py"]
<<: *read_query_modules_cluster
- name: "update-query-modules"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/lba_procedures/procedures/"
args: ["lba_procedures/update_query_modules.py"]
<<: *update_query_modules_cluster
- name: "create-delete-query-modules"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/lba_procedures/procedures/"
args: ["lba_procedures/create_delete_query_modules.py"]
<<: *create_delete_query_modules_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
- name: "read-permission-queries"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/lba_procedures/procedures/"
args: ["lba_procedures/read_permission_queries.py"]
<<: *read_permission_queries
- name: "update-permission-queries"
binary: "tests/e2e/pytest_runner.sh"
proc: "tests/e2e/lba_procedures/procedures/"
args: ["lba_procedures/update_permission_queries.py"]
<<: *update_permission_queries_cluster

View File

@ -10,6 +10,9 @@ add_subdirectory(transactions)
# auth test binaries
add_subdirectory(auth)
# lba test binaries
add_subdirectory(fine_grained_access)
## distributed ha/basic binaries
#add_subdirectory(ha/basic)
#

View File

@ -0,0 +1,11 @@
set(target_name memgraph__integration__fine_grained_access)
set(tester_target_name ${target_name}__tester)
set(filtering_target_name ${target_name}__filtering)
add_executable(${tester_target_name} tester.cpp)
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
target_link_libraries(${tester_target_name} mg-communication)
add_executable(${filtering_target_name} filtering.cpp)
set_target_properties(${filtering_target_name} PROPERTIES OUTPUT_NAME filtering)
target_link_libraries(${filtering_target_name} mg-communication)

View File

@ -0,0 +1,59 @@
// 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 <cstdlib>
#include "communication/bolt/client.hpp"
#include "io/network/endpoint.hpp"
#include "io/network/utils.hpp"
#include "utils/logging.hpp"
DEFINE_string(address, "127.0.0.1", "Server address");
DEFINE_int32(port, 7687, "Server port");
DEFINE_string(username, "admin", "Username for the database");
DEFINE_string(password, "admin", "Password for the database");
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
/**
* Verifies that user 'user' has privileges that are given as positional
* arguments.
*/
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
memgraph::communication::SSLInit sslInit;
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
memgraph::communication::ClientContext context(FLAGS_use_ssl);
memgraph::communication::bolt::Client client(context);
client.Connect(endpoint, FLAGS_username, FLAGS_password);
try {
std::string query(argv[1]);
auto ret = client.Execute(query, {});
uint64_t count_got = ret.records.size();
if (count_got != std::atoi(argv[2])) {
LOG_FATAL("Expected the record to have {} entries but they had {} entries!", argv[2], count_got);
}
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
LOG_FATAL(
"The query shoudn't have failed but it failed with an "
"error message '{}', {}",
e.what(), argv[0]);
}
return 0;
}

View File

@ -0,0 +1,135 @@
#!/usr/bin/python3 -u
# Copyright 2021 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 argparse
import atexit
import os
import subprocess
import sys
import tempfile
import time
from typing import List
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
UNAUTHORIZED_ERROR = "You are not authorized to execute this query! Please " "contact your database administrator."
def wait_for_server(port, delay=0.1):
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
while subprocess.call(cmd) != 0:
time.sleep(0.01)
time.sleep(delay)
def execute_tester(
binary, queries, should_fail=False, failure_message="", username="", password="", check_failure=True
):
args = [binary, "--username", username, "--password", password]
if should_fail:
args.append("--should-fail")
if failure_message:
args.extend(["--failure-message", failure_message])
if check_failure:
args.append("--check-failure")
args.extend(queries)
subprocess.run(args).check_returncode()
def execute_filtering(binary: str, queries: List[str], expected: int, username: str = "", password: str = "") -> None:
args = [binary, "--username", username, "--password", password]
args.extend(queries)
args.append(str(expected))
subprocess.run(args).check_returncode()
def execute_test(memgraph_binary: str, tester_binary: str, filtering_binary: str) -> None:
storage_directory = tempfile.TemporaryDirectory()
memgraph_args = [memgraph_binary, "--data-directory", storage_directory.name]
def execute_admin_queries(queries):
return execute_tester(
tester_binary, queries, should_fail=False, check_failure=True, username="admin", password="admin"
)
def execute_user_queries(queries, should_fail=False, failure_message="", check_failure=True):
return execute_tester(tester_binary, queries, should_fail, failure_message, "user", "user", check_failure)
# Start the memgraph binary
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
time.sleep(0.1)
assert memgraph.poll() is None, "Memgraph process died prematurely!"
wait_for_server(7687)
# Register cleanup function
@atexit.register
def cleanup():
if memgraph.poll() is None:
memgraph.terminate()
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
# Prepare all users
execute_admin_queries(
[
"CREATE USER admin IDENTIFIED BY 'admin'",
"GRANT ALL PRIVILEGES TO admin",
"CREATE USER user IDENTIFIED BY 'user'",
"GRANT ALL PRIVILEGES TO user",
"GRANT LABELS :label1, :label2, :label3 TO user",
"GRANT EDGE_TYPES :edgeType1, :edgeType2 TO user",
"MERGE (l1:label1 {name: 'test1'})",
"MERGE (l2:label2 {name: 'test2'})",
"MATCH (l1:label1),(l2:label2) WHERE l1.name = 'test1' AND l2.name = 'test2' CREATE (l1)-[r:edgeType1]->(l2)",
"MERGE (l3:label3 {name: 'test3'})",
"MATCH (l1:label1),(l3:label3) WHERE l1.name = 'test1' AND l3.name = 'test3' CREATE (l1)-[r:edgeType2]->(l3)",
"MERGE (mix:label3:label1 {name: 'test4'})",
"MATCH (l1:label1),(mix:label3) WHERE l1.name = 'test1' AND mix.name = 'test4' CREATE (l1)-[r:edgeType2]->(mix)",
]
)
# Run the test with all combinations of permissions
print("\033[1;36m~~ Starting edge filtering test ~~\033[0m")
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 3, "user", "user")
execute_admin_queries(["DENY EDGE_TYPES :edgeType1 TO user"])
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 2, "user", "user")
execute_admin_queries(["GRANT EDGE_TYPES :edgeType1 TO user", "DENY LABELS :label3 TO user"])
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 1, "user", "user")
execute_admin_queries(["DENY LABELS :label1 TO user"])
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 0, "user", "user")
execute_admin_queries(["REVOKE LABELS * FROM user", "REVOKE EDGE_TYPES * FROM user"])
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 0, "user", "user")
print("\033[1;36m~~ Finished edge filtering test ~~\033[0m\n")
# Shutdown the memgraph binary
memgraph.terminate()
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
if __name__ == "__main__":
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "tester")
filtering_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "filtering")
parser = argparse.ArgumentParser()
parser.add_argument("--memgraph", default=memgraph_binary)
parser.add_argument("--tester", default=tester_binary)
parser.add_argument("--filtering", default=filtering_binary)
args = parser.parse_args()
execute_test(args.memgraph, args.tester, args.filtering)
sys.exit(0)

View File

@ -0,0 +1,84 @@
// 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 "communication/bolt/client.hpp"
#include "io/network/endpoint.hpp"
#include "io/network/utils.hpp"
DEFINE_string(address, "127.0.0.1", "Server address");
DEFINE_int32(port, 7687, "Server port");
DEFINE_string(username, "", "Username for the database");
DEFINE_string(password, "", "Password for the database");
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
DEFINE_string(failure_message, "", "Set to the expected failure message.");
/**
* Executes queries passed as positional arguments and verifies whether they
* succeeded, failed, failed with a specific error message or executed without a
* specific error occurring.
*/
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
memgraph::communication::SSLInit sslInit;
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
memgraph::communication::ClientContext context(FLAGS_use_ssl);
memgraph::communication::bolt::Client client(context);
client.Connect(endpoint, FLAGS_username, FLAGS_password);
for (int i = 1; i < argc; ++i) {
std::string query(argv[i]);
try {
client.Execute(query, {});
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
if (!FLAGS_check_failure) {
if (!FLAGS_failure_message.empty() && e.what() == FLAGS_failure_message) {
LOG_FATAL(
"The query should have succeeded or failed with an error "
"message that isn't equal to '{}' but it failed with that error "
"message",
FLAGS_failure_message);
}
continue;
}
if (FLAGS_should_fail) {
if (!FLAGS_failure_message.empty() && e.what() != FLAGS_failure_message) {
LOG_FATAL(
"The query should have failed with an error message of '{}'' but "
"instead it failed with '{}'",
FLAGS_failure_message, e.what());
}
return 0;
} else {
LOG_FATAL(
"The query shoudn't have failed but it failed with an "
"error message '{}'",
e.what());
}
}
if (!FLAGS_check_failure) continue;
if (FLAGS_should_fail) {
LOG_FATAL(
"The query should have failed but instead it executed "
"successfully!");
}
}
return 0;
}

View File

@ -26,6 +26,9 @@ import log
import helpers
import runners
WITH_FINE_GRAINED_AUTHORIZATION = "with_fine_grained_authorization"
WITHOUT_FINE_GRAINED_AUTHORIZATION = "without_fine_grained_authorization"
def get_queries(gen, count):
# Make the generator deterministic.
@ -37,8 +40,7 @@ def get_queries(gen, count):
return ret
def match_patterns(dataset, variant, group, test, is_default_variant,
patterns):
def match_patterns(dataset, variant, group, test, is_default_variant, patterns):
for pattern in patterns:
verdict = [fnmatch.fnmatchcase(dataset, pattern[0])]
if pattern[1] != "":
@ -58,7 +60,7 @@ def filter_benchmarks(generators, patterns):
pattern = patterns[i].split("/")
if len(pattern) > 4 or len(pattern) == 0:
raise Exception("Invalid benchmark description '" + pattern + "'!")
pattern.extend(["", "*", "*"][len(pattern) - 1:])
pattern.extend(["", "*", "*"][len(pattern) - 1 :])
patterns[i] = pattern
filtered = []
for dataset in sorted(generators.keys()):
@ -68,8 +70,7 @@ def filter_benchmarks(generators, patterns):
current = collections.defaultdict(list)
for group in tests:
for test_name, test_func in tests[group]:
if match_patterns(dataset, variant, group, test_name,
is_default_variant, patterns):
if match_patterns(dataset, variant, group, test_name, is_default_variant, patterns):
current[group].append((test_name, test_func))
if len(current) > 0:
filtered.append((generator(variant), dict(current)))
@ -79,43 +80,72 @@ def filter_benchmarks(generators, patterns):
# Parse options.
parser = argparse.ArgumentParser(
description="Memgraph benchmark executor.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("benchmarks", nargs="*", default="",
help="descriptions of benchmarks that should be run; "
"multiple descriptions can be specified to run multiple "
"benchmarks; the description is specified as "
"dataset/variant/group/test; Unix shell-style wildcards "
"can be used in the descriptions; variant, group and test "
"are optional and they can be left out; the default "
"variant is '' which selects the default dataset variant; "
"the default group is '*' which selects all groups; the "
"default test is '*' which selects all tests")
parser.add_argument("--memgraph-binary",
default=helpers.get_binary_path("memgraph"),
help="Memgraph binary used for benchmarking")
parser.add_argument("--client-binary",
default=helpers.get_binary_path("tests/mgbench/client"),
help="client binary used for benchmarking")
parser.add_argument("--num-workers-for-import", type=int,
default=multiprocessing.cpu_count() // 2,
help="number of workers used to import the dataset")
parser.add_argument("--num-workers-for-benchmark", type=int,
default=1,
help="number of workers used to execute the benchmark")
parser.add_argument("--single-threaded-runtime-sec", type=int,
default=10,
help="single threaded duration of each test")
parser.add_argument("--no-load-query-counts", action="store_true",
help="disable loading of cached query counts")
parser.add_argument("--no-save-query-counts", action="store_true",
help="disable storing of cached query counts")
parser.add_argument("--export-results", default="",
help="file path into which results should be exported")
parser.add_argument("--temporary-directory", default="/tmp",
help="directory path where temporary data should "
"be stored")
parser.add_argument("--no-properties-on-edges", action="store_true",
help="disable properties on edges")
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument(
"benchmarks",
nargs="*",
default="",
help="descriptions of benchmarks that should be run; "
"multiple descriptions can be specified to run multiple "
"benchmarks; the description is specified as "
"dataset/variant/group/test; Unix shell-style wildcards "
"can be used in the descriptions; variant, group and test "
"are optional and they can be left out; the default "
"variant is '' which selects the default dataset variant; "
"the default group is '*' which selects all groups; the "
"default test is '*' which selects all tests",
)
parser.add_argument(
"--memgraph-binary",
default=helpers.get_binary_path("memgraph"),
help="Memgraph binary used for benchmarking",
)
parser.add_argument(
"--client-binary",
default=helpers.get_binary_path("tests/mgbench/client"),
help="client binary used for benchmarking",
)
parser.add_argument(
"--num-workers-for-import",
type=int,
default=multiprocessing.cpu_count() // 2,
help="number of workers used to import the dataset",
)
parser.add_argument(
"--num-workers-for-benchmark",
type=int,
default=1,
help="number of workers used to execute the benchmark",
)
parser.add_argument(
"--single-threaded-runtime-sec",
type=int,
default=10,
help="single threaded duration of each test",
)
parser.add_argument(
"--no-load-query-counts",
action="store_true",
help="disable loading of cached query counts",
)
parser.add_argument(
"--no-save-query-counts",
action="store_true",
help="disable storing of cached query counts",
)
parser.add_argument(
"--export-results",
default="",
help="file path into which results should be exported",
)
parser.add_argument(
"--temporary-directory",
default="/tmp",
help="directory path where temporary data should " "be stored",
)
parser.add_argument("--no-properties-on-edges", action="store_true", help="disable properties on edges")
parser.add_argument("--bolt-port", default=7687, help="memgraph bolt port")
args = parser.parse_args()
# Detect available datasets.
@ -124,8 +154,7 @@ for key in dir(datasets):
if key.startswith("_"):
continue
dataset = getattr(datasets, key)
if not inspect.isclass(dataset) or dataset == datasets.Dataset or \
not issubclass(dataset, datasets.Dataset):
if not inspect.isclass(dataset) or dataset == datasets.Dataset or not issubclass(dataset, datasets.Dataset):
continue
tests = collections.defaultdict(list)
for funcname in dir(dataset):
@ -135,8 +164,9 @@ for key in dir(datasets):
tests[group].append((test, funcname))
generators[dataset.NAME] = (dataset, dict(tests))
if dataset.PROPERTIES_ON_EDGES and args.no_properties_on_edges:
raise Exception("The \"{}\" dataset requires properties on edges, "
"but you have disabled them!".format(dataset.NAME))
raise Exception(
'The "{}" dataset requires properties on edges, ' "but you have disabled them!".format(dataset.NAME)
)
# List datasets if there is no specified dataset.
if len(args.benchmarks) == 0:
@ -144,8 +174,11 @@ if len(args.benchmarks) == 0:
for name in sorted(generators.keys()):
print("Dataset:", name)
dataset, tests = generators[name]
print(" Variants:", ", ".join(dataset.VARIANTS),
"(default: " + dataset.DEFAULT_VARIANT + ")")
print(
" Variants:",
", ".join(dataset.VARIANTS),
"(default: " + dataset.DEFAULT_VARIANT + ")",
)
for group in sorted(tests.keys()):
print(" Group:", group)
for test_name, test_func in tests[group]:
@ -162,34 +195,45 @@ results = helpers.RecursiveDict()
# Filter out the generators.
benchmarks = filter_benchmarks(generators, args.benchmarks)
# Run all specified benchmarks.
for dataset, tests in benchmarks:
log.init("Preparing", dataset.NAME + "/" + dataset.get_variant(),
"dataset")
dataset.prepare(cache.cache_directory("datasets", dataset.NAME,
dataset.get_variant()))
log.init("Preparing", dataset.NAME + "/" + dataset.get_variant(), "dataset")
dataset.prepare(cache.cache_directory("datasets", dataset.NAME, dataset.get_variant()))
# Prepare runners and import the dataset.
memgraph = runners.Memgraph(args.memgraph_binary, args.temporary_directory,
not args.no_properties_on_edges)
client = runners.Client(args.client_binary, args.temporary_directory)
memgraph = runners.Memgraph(
args.memgraph_binary,
args.temporary_directory,
not args.no_properties_on_edges,
args.bolt_port,
)
client = runners.Client(args.client_binary, args.temporary_directory, args.bolt_port)
memgraph.start_preparation()
ret = client.execute(file_path=dataset.get_file(),
num_workers=args.num_workers_for_import)
ret = client.execute(file_path=dataset.get_file(), num_workers=args.num_workers_for_import)
usage = memgraph.stop()
# Display import statistics.
print()
for row in ret:
print("Executed", row["count"], "queries in", row["duration"],
"seconds using", row["num_workers"],
"workers with a total throughput of", row["throughput"],
"queries/second.")
print(
"Executed",
row["count"],
"queries in",
row["duration"],
"seconds using",
row["num_workers"],
"workers with a total throughput of",
row["throughput"],
"queries/second.",
)
print()
print("The database used", usage["cpu"],
"seconds of CPU time and peaked at",
usage["memory"] / 1024 / 1024, "MiB of RAM.")
print(
"The database used",
usage["cpu"],
"seconds of CPU time and peaked at",
usage["memory"] / 1024 / 1024,
"MiB of RAM.",
)
# Save import results.
import_key = [dataset.NAME, dataset.get_variant(), "__import__"]
@ -198,87 +242,128 @@ for dataset, tests in benchmarks:
# TODO: cache import data
# Run all benchmarks in all available groups.
for group in sorted(tests.keys()):
for test, funcname in tests[group]:
log.info("Running test:", "{}/{}".format(group, test))
func = getattr(dataset, funcname)
# Get number of queries to execute.
# TODO: implement minimum number of queries, `max(10, num_workers)`
config_key = [dataset.NAME, dataset.get_variant(), group, test]
cached_count = config.get_value(*config_key)
if cached_count is None:
print("Determining the number of queries necessary for",
args.single_threaded_runtime_sec,
"seconds of single-threaded runtime...")
# First run to prime the query caches.
for with_fine_grained_authorization in [False, True]:
if with_fine_grained_authorization:
memgraph.start_preparation()
client.execute(file_path=dataset.get_file(), num_workers=args.num_workers_for_import)
client.execute(
queries=[
("CREATE USER user IDENTIFIED BY 'test';", {}),
("GRANT ALL PRIVILEGES TO user;", {}),
("GRANT CREATE_DELETE ON EDGE_TYPES * TO user;", {}),
("GRANT CREATE_DELETE ON LABELS * TO user;", {}),
]
)
client = runners.Client(
args.client_binary,
args.temporary_directory,
args.bolt_port,
username="user",
password="test",
)
memgraph.stop()
test_type = (
WITH_FINE_GRAINED_AUTHORIZATION if with_fine_grained_authorization else WITHOUT_FINE_GRAINED_AUTHORIZATION
)
for group in sorted(tests.keys()):
for test, funcname in tests[group]:
log.info("Running test:", "{}/{}/{}".format(group, test, test_type))
func = getattr(dataset, funcname)
# Get number of queries to execute.
# TODO: implement minimum number of queries, `max(10, num_workers)`
config_key = [dataset.NAME, dataset.get_variant(), group, test, test_type]
cached_count = config.get_value(*config_key)
if cached_count is None:
print(
"Determining the number of queries necessary for",
args.single_threaded_runtime_sec,
"seconds of single-threaded runtime...",
)
# First run to prime the query caches.
memgraph.start_benchmark()
client.execute(queries=get_queries(func, 1), num_workers=1)
# Get a sense of the runtime.
count = 1
while True:
ret = client.execute(queries=get_queries(func, count), num_workers=1)
duration = ret[0]["duration"]
should_execute = int(args.single_threaded_runtime_sec / (duration / count))
print(
"executed_queries={}, total_duration={}, "
"query_duration={}, estimated_count={}".format(
count, duration, duration / count, should_execute
)
)
# We don't have to execute the next iteration when
# `should_execute` becomes the same order of magnitude as
# `count * 10`.
if should_execute / (count * 10) < 10:
count = should_execute
break
else:
count = count * 10
memgraph.stop()
config.set_value(
*config_key,
value={
"count": count,
"duration": args.single_threaded_runtime_sec,
},
)
else:
print(
"Using cached query count of",
cached_count["count"],
"queries for",
cached_count["duration"],
"seconds of single-threaded runtime.",
)
count = int(cached_count["count"] * args.single_threaded_runtime_sec / cached_count["duration"])
# Benchmark run.
print("Sample query:", get_queries(func, 1)[0][0])
print(
"Executing benchmark with",
count,
"queries that should " "yield a single-threaded runtime of",
args.single_threaded_runtime_sec,
"seconds.",
)
print(
"Queries are executed using",
args.num_workers_for_benchmark,
"concurrent clients.",
)
memgraph.start_benchmark()
client.execute(queries=get_queries(func, 1), num_workers=1)
# Get a sense of the runtime.
count = 1
while True:
ret = client.execute(queries=get_queries(func, count),
num_workers=1)
duration = ret[0]["duration"]
should_execute = int(args.single_threaded_runtime_sec /
(duration / count))
print("executed_queries={}, total_duration={}, "
"query_duration={}, estimated_count={}".format(
count, duration, duration / count,
should_execute))
# We don't have to execute the next iteration when
# `should_execute` becomes the same order of magnitude as
# `count * 10`.
if should_execute / (count * 10) < 10:
count = should_execute
break
else:
count = count * 10
memgraph.stop()
config.set_value(*config_key, value={
"count": count,
"duration": args.single_threaded_runtime_sec})
else:
print("Using cached query count of", cached_count["count"],
"queries for", cached_count["duration"],
"seconds of single-threaded runtime.")
count = int(cached_count["count"] *
args.single_threaded_runtime_sec /
cached_count["duration"])
ret = client.execute(
queries=get_queries(func, count),
num_workers=args.num_workers_for_benchmark,
)[0]
usage = memgraph.stop()
ret["database"] = usage
# Benchmark run.
print("Sample query:", get_queries(func, 1)[0][0])
print("Executing benchmark with", count, "queries that should "
"yield a single-threaded runtime of",
args.single_threaded_runtime_sec, "seconds.")
print("Queries are executed using", args.num_workers_for_benchmark,
"concurrent clients.")
memgraph.start_benchmark()
ret = client.execute(queries=get_queries(func, count),
num_workers=args.num_workers_for_benchmark)[0]
usage = memgraph.stop()
ret["database"] = usage
# Output summary.
print()
print("Executed", ret["count"], "queries in", ret["duration"], "seconds.")
print("Queries have been retried", ret["retries"], "times.")
print("Database used {:.3f} seconds of CPU time.".format(usage["cpu"]))
print("Database peaked at {:.3f} MiB of memory.".format(usage["memory"] / 1024.0 / 1024.0))
print("{:<31} {:>20} {:>20} {:>20}".format("Metadata:", "min", "avg", "max"))
metadata = ret["metadata"]
for key in sorted(metadata.keys()):
print(
"{name:>30}: {minimum:>20.06f} {average:>20.06f} "
"{maximum:>20.06f}".format(name=key, **metadata[key])
)
log.success("Throughput: {:02f} QPS".format(ret["throughput"]))
# Output summary.
print()
print("Executed", ret["count"], "queries in",
ret["duration"], "seconds.")
print("Queries have been retried", ret["retries"], "times.")
print("Database used {:.3f} seconds of CPU time.".format(
usage["cpu"]))
print("Database peaked at {:.3f} MiB of memory.".format(
usage["memory"] / 1024.0 / 1024.0))
print("{:<31} {:>20} {:>20} {:>20}".format("Metadata:", "min",
"avg", "max"))
metadata = ret["metadata"]
for key in sorted(metadata.keys()):
print("{name:>30}: {minimum:>20.06f} {average:>20.06f} "
"{maximum:>20.06f}".format(name=key, **metadata[key]))
log.success("Throughput: {:02f} QPS".format(ret["throughput"]))
# Save results.
results_key = [dataset.NAME, dataset.get_variant(), group, test]
results.set_value(*results_key, value=ret)
# Save results.
results_key = [dataset.NAME, dataset.get_variant(), group, test, test_type]
results.set_value(*results_key, value=ret)
# Save configuration.
if not args.no_save_query_counts:

View File

@ -77,7 +77,7 @@ def recursive_get(data, *args, value=None):
return data
def compare_results(results_from, results_to, fields):
def compare_results(results_from, results_to, fields, ignored):
ret = {}
for dataset, variants in results_to.items():
for variant, groups in variants.items():
@ -85,39 +85,44 @@ def compare_results(results_from, results_to, fields):
if group == "__import__":
continue
for scenario, summary_to in scenarios.items():
summary_from = recursive_get(
results_from, dataset, variant, group, scenario,
value={})
if len(summary_from) > 0 and \
summary_to["count"] != summary_from["count"] or \
summary_to["num_workers"] != \
summary_from["num_workers"]:
if scenario in ignored:
continue
summary_from = recursive_get(results_from, dataset, variant, group, scenario, value={})
if (
len(summary_from) > 0
and summary_to["count"] != summary_from["count"]
or summary_to["num_workers"] != summary_from["num_workers"]
):
raise Exception("Incompatible results!")
testcode = "/".join([dataset, variant, group, scenario,
"{:02d}".format(
summary_to["num_workers"])])
testcode = "/".join(
[
dataset,
variant,
group,
scenario,
"{:02d}".format(summary_to["num_workers"]),
]
)
row = {}
performance_changed = False
for field in fields:
key = field["name"]
if key in summary_to:
row[key] = compute_diff(
summary_from.get(key, None),
summary_to[key])
row[key] = compute_diff(summary_from.get(key, None), summary_to[key])
elif key in summary_to["database"]:
row[key] = compute_diff(
recursive_get(summary_from, "database", key,
value=None),
summary_to["database"][key])
recursive_get(summary_from, "database", key, value=None),
summary_to["database"][key],
)
else:
row[key] = compute_diff(
recursive_get(summary_from, "metadata", key,
"average", value=None),
summary_to["metadata"][key]["average"])
if "diff" not in row[key] or \
("diff_treshold" in field and
abs(row[key]["diff"]) >=
field["diff_treshold"]):
recursive_get(summary_from, "metadata", key, "average", value=None),
summary_to["metadata"][key]["average"],
)
if "diff" not in row[key] or (
"diff_treshold" in field and abs(row[key]["diff"]) >= field["diff_treshold"]
):
performance_changed = True
if performance_changed:
ret[testcode] = row
@ -130,8 +135,15 @@ def generate_remarkup(fields, data):
ret += "<table>\n"
ret += " <tr>\n"
ret += " <th>Testcode</th>\n"
ret += "\n".join(map(lambda x: " <th>{}</th>".format(
x["name"].replace("_", " ").capitalize()), fields)) + "\n"
ret += (
"\n".join(
map(
lambda x: " <th>{}</th>".format(x["name"].replace("_", " ").capitalize()),
fields,
)
)
+ "\n"
)
ret += " </tr>\n"
for testcode in sorted(data.keys()):
ret += " <tr>\n"
@ -147,12 +159,9 @@ def generate_remarkup(fields, data):
else:
color = "red"
sign = "{{icon {} color={}}}".format(arrow, color)
ret += " <td>{:.3f}{} //({:+.2%})// {}</td>\n".format(
value, field["unit"], diff, sign)
ret += ' <td bgcolor="{}">{:.3f}{} ({:+.2%})</td>\n'.format(color, value, field["unit"], diff)
else:
ret += " <td>{:.3f}{} //(new)// " \
"{{icon plus color=blue}}</td>\n".format(
value, field["unit"])
ret += '<td bgcolor="blue">{:.3f}{} //(new)// </td>\n'.format(value, field["unit"])
ret += " </tr>\n"
ret += "</table>\n"
else:
@ -161,22 +170,33 @@ def generate_remarkup(fields, data):
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Compare results of multiple benchmark runs.")
parser.add_argument("--compare", action="append", nargs=2,
metavar=("from", "to"),
help="compare results between `from` and `to` files")
parser = argparse.ArgumentParser(description="Compare results of multiple benchmark runs.")
parser.add_argument(
"--compare",
action="append",
nargs=2,
metavar=("from", "to"),
help="compare results between `from` and `to` files",
)
parser.add_argument("--output", default="", help="output file name")
# file is read line by line, each representing one test name
parser.add_argument("--exclude_tests_file", help="file listing test names to be excluded")
args = parser.parse_args()
if args.compare is None or len(args.compare) == 0:
raise Exception("You must specify at least one pair of files!")
if args.exclude_tests_file:
with open(args.exclude_tests_file, "r") as f:
ignored = [line.rstrip("\n") for line in f]
else:
ignored = []
data = {}
for file_from, file_to in args.compare:
results_from = load_results(file_from)
results_to = load_results(file_to)
data.update(compare_results(results_from, results_to, FIELDS))
data.update(compare_results(results_from, results_to, FIELDS, ignored))
remarkup = generate_remarkup(FIELDS, data)
if args.output:

View File

@ -135,10 +135,7 @@ class Pokec(Dataset):
return ("MATCH (n:User {id : $id}) RETURN n", {"id": self._get_random_vertex()})
def benchmark__arango__single_vertex_write(self):
return (
"CREATE (n:UserTemp {id : $id}) RETURN n",
{"id": random.randint(1, self._num_vertices * 10)},
)
return ("CREATE (n:UserTemp {id : $id}) RETURN n", {"id": random.randint(1, self._num_vertices * 10)})
def benchmark__arango__single_edge_write(self):
vertex_from, vertex_to = self._get_random_from_to()
@ -154,10 +151,7 @@ class Pokec(Dataset):
return ("MATCH (n:User) WHERE n.age >= 18 RETURN n.age, COUNT(*)", {})
def benchmark__arango__expansion_1(self):
return (
"MATCH (s:User {id: $id})-->(n:User) " "RETURN n.id",
{"id": self._get_random_vertex()},
)
return ("MATCH (s:User {id: $id})-->(n:User) " "RETURN n.id", {"id": self._get_random_vertex()})
def benchmark__arango__expansion_1_with_filter(self):
return (
@ -166,10 +160,7 @@ class Pokec(Dataset):
)
def benchmark__arango__expansion_2(self):
return (
"MATCH (s:User {id: $id})-->()-->(n:User) " "RETURN DISTINCT n.id",
{"id": self._get_random_vertex()},
)
return ("MATCH (s:User {id: $id})-->()-->(n:User) " "RETURN DISTINCT n.id", {"id": self._get_random_vertex()})
def benchmark__arango__expansion_2_with_filter(self):
return (
@ -202,10 +193,7 @@ class Pokec(Dataset):
)
def benchmark__arango__neighbours_2(self):
return (
"MATCH (s:User {id: $id})-[*1..2]->(n:User) " "RETURN DISTINCT n.id",
{"id": self._get_random_vertex()},
)
return ("MATCH (s:User {id: $id})-[*1..2]->(n:User) " "RETURN DISTINCT n.id", {"id": self._get_random_vertex()})
def benchmark__arango__neighbours_2_with_filter(self):
return (
@ -282,10 +270,7 @@ class Pokec(Dataset):
return ("MATCH (n) RETURN min(n.age), max(n.age), avg(n.age)", {})
def benchmark__match__pattern_cycle(self):
return (
"MATCH (n:User {id: $id})-[e1]->(m)-[e2]->(n) " "RETURN e1, m, e2",
{"id": self._get_random_vertex()},
)
return ("MATCH (n:User {id: $id})-[e1]->(m)-[e2]->(n) " "RETURN e1, m, e2", {"id": self._get_random_vertex()})
def benchmark__match__pattern_long(self):
return (
@ -294,19 +279,16 @@ class Pokec(Dataset):
)
def benchmark__match__pattern_short(self):
return (
"MATCH (n:User {id: $id})-[e]->(m) " "RETURN m LIMIT 1",
{"id": self._get_random_vertex()},
)
return ("MATCH (n:User {id: $id})-[e]->(m) " "RETURN m LIMIT 1", {"id": self._get_random_vertex()})
def benchmark__match__vertex_on_label_property(self):
return (
"MATCH (n:User) WITH n WHERE n.id = $id RETURN n",
{"id": self._get_random_vertex()},
)
return ("MATCH (n:User) WITH n WHERE n.id = $id RETURN n", {"id": self._get_random_vertex()})
def benchmark__match__vertex_on_label_property_index(self):
return ("MATCH (n:User {id: $id}) RETURN n", {"id": self._get_random_vertex()})
def benchmark__match__vertex_on_property(self):
return ("MATCH (n {id: $id}) RETURN n", {"id": self._get_random_vertex()})
def benchmark__update__vertex_on_property(self):
return ("MATCH (n {id: $id}) SET n.property = -1", {"id": self._get_random_vertex()})

View File

@ -51,11 +51,12 @@ def _get_usage(pid):
class Memgraph:
def __init__(self, memgraph_binary, temporary_dir, properties_on_edges):
def __init__(self, memgraph_binary, temporary_dir, properties_on_edges, bolt_port):
self._memgraph_binary = memgraph_binary
self._directory = tempfile.TemporaryDirectory(dir=temporary_dir)
self._properties_on_edges = properties_on_edges
self._proc_mg = None
self._bolt_port = bolt_port
atexit.register(self._cleanup)
# Determine Memgraph version
@ -69,6 +70,7 @@ class Memgraph:
def _get_args(self, **kwargs):
data_directory = os.path.join(self._directory.name, "memgraph")
kwargs["bolt_port"] = self._bolt_port
if self._memgraph_version >= (0, 50, 0):
kwargs["data_directory"] = data_directory
else:
@ -88,7 +90,7 @@ class Memgraph:
if self._proc_mg.poll() is not None:
self._proc_mg = None
raise Exception("The database process died prematurely!")
wait_for_server(7687)
wait_for_server(self._bolt_port)
ret = self._proc_mg.poll()
assert ret is None, "The database process died prematurely " "({})!".format(ret)
@ -121,9 +123,14 @@ class Memgraph:
class Client:
def __init__(self, client_binary, temporary_directory):
def __init__(
self, client_binary: str, temporary_directory: str, bolt_port: int, username: str = "", password: str = ""
):
self._client_binary = client_binary
self._directory = tempfile.TemporaryDirectory(dir=temporary_directory)
self._username = username
self._password = password
self._bolt_port = bolt_port
def _get_args(self, **kwargs):
return _convert_args_to_flags(self._client_binary, **kwargs)
@ -144,8 +151,15 @@ class Client:
json.dump(query, f)
f.write("\n")
args = self._get_args(input=file_path, num_workers=num_workers, queries_json=queries_json)
args = self._get_args(
input=file_path,
num_workers=num_workers,
queries_json=queries_json,
username=self._username,
password=self._password,
port=self._bolt_port,
)
ret = subprocess.run(args, stdout=subprocess.PIPE, check=True)
data = ret.stdout.decode("utf-8").strip().split("\n")
data = [x for x in data if not x.startswith("[")]
# data = [x for x in data if not x.startswith("[")]
return list(map(json.loads, data))

View File

@ -70,13 +70,16 @@ target_link_libraries(${test_prefix}mgp_trans_c_api mg-query)
# Test mg-query
add_unit_test(bfs_single_node.cpp)
target_link_libraries(${test_prefix}bfs_single_node mg-query)
target_link_libraries(${test_prefix}bfs_single_node mg-query mg-glue)
add_unit_test(bfs_fine_grained.cpp)
target_link_libraries(${test_prefix}bfs_fine_grained mg-query mg-glue)
add_unit_test(cypher_main_visitor.cpp)
target_link_libraries(${test_prefix}cypher_main_visitor mg-query)
add_unit_test(interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/communication.cpp)
target_link_libraries(${test_prefix}interpreter mg-communication mg-query)
target_link_libraries(${test_prefix}interpreter mg-communication mg-query mg-glue)
add_unit_test(plan_pretty_print.cpp)
target_link_libraries(${test_prefix}plan_pretty_print mg-query)
@ -94,32 +97,32 @@ add_unit_test(query_plan.cpp)
target_link_libraries(${test_prefix}query_plan mg-query)
add_unit_test(query_plan_accumulate_aggregate.cpp)
target_link_libraries(${test_prefix}query_plan_accumulate_aggregate mg-query)
target_link_libraries(${test_prefix}query_plan_accumulate_aggregate mg-query mg-glue)
add_unit_test(query_plan_bag_semantics.cpp)
target_link_libraries(${test_prefix}query_plan_bag_semantics mg-query)
target_link_libraries(${test_prefix}query_plan_bag_semantics mg-query mg-glue)
add_unit_test(query_plan_create_set_remove_delete.cpp)
target_link_libraries(${test_prefix}query_plan_create_set_remove_delete mg-query)
target_link_libraries(${test_prefix}query_plan_create_set_remove_delete mg-query mg-glue)
add_unit_test(query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/communication.cpp)
target_link_libraries(${test_prefix}query_plan_edge_cases mg-communication mg-query)
add_unit_test(query_plan_match_filter_return.cpp)
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query)
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query mg-query mg-glue)
add_unit_test(query_plan_read_write_typecheck.cpp
${CMAKE_SOURCE_DIR}/src/query/plan/read_write_type_checker.cpp)
target_link_libraries(${test_prefix}query_plan_read_write_typecheck mg-query)
add_unit_test(query_plan_v2_create_set_remove_delete.cpp)
target_link_libraries(${test_prefix}query_plan_v2_create_set_remove_delete mg-query)
target_link_libraries(${test_prefix}query_plan_v2_create_set_remove_delete mg-query mg-glue)
add_unit_test(query_pretty_print.cpp)
target_link_libraries(${test_prefix}query_pretty_print mg-query)
add_unit_test(query_trigger.cpp)
target_link_libraries(${test_prefix}query_trigger mg-query)
target_link_libraries(${test_prefix}query_trigger mg-query mg-glue)
add_unit_test(query_serialization_property_value.cpp)
target_link_libraries(${test_prefix}query_serialization_property_value mg-query)
@ -160,7 +163,7 @@ add_unit_test(query_semantic.cpp)
target_link_libraries(${test_prefix}query_semantic mg-query)
add_unit_test(query_variable_start_planner.cpp)
target_link_libraries(${test_prefix}query_variable_start_planner mg-query)
target_link_libraries(${test_prefix}query_variable_start_planner mg-query mg-glue)
add_unit_test(stripped.cpp)
target_link_libraries(${test_prefix}stripped mg-query)
@ -321,6 +324,12 @@ target_link_libraries(${test_prefix}storage_v2_isolation_level mg-storage-v2)
add_unit_test(replication_persistence_helper.cpp)
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)
add_unit_test(cpp_api.cpp)
target_link_libraries(${test_prefix}cpp_api mg-query)
target_include_directories(${test_prefix}cpp_api PRIVATE ${CMAKE_SOURCE_DIR}/include)

View File

@ -11,12 +11,14 @@
#include <algorithm>
#include <iostream>
#include <optional>
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include "auth/auth.hpp"
#include "auth/crypto.hpp"
#include "auth/models.hpp"
#include "utils/cast.hpp"
#include "utils/file.hpp"
#include "utils/license.hpp"
@ -159,6 +161,79 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
}
}
#ifdef MG_ENTERPRISE
TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
ASSERT_FALSE(auth.HasUsers());
ASSERT_TRUE(auth.AddUser("test"));
ASSERT_TRUE(auth.HasUsers());
auto user = auth.GetUser("test");
ASSERT_NE(user, std::nullopt);
// Test initial user fine grained access permissions.
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), FineGrainedAccessPermissions{});
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(), FineGrainedAccessPermissions{});
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
user->GetFineGrainedAccessEdgeTypePermissions());
// Grant one label to user .
user->fine_grained_access_handler().label_permissions().Grant("labelTest", FineGrainedPermission::CREATE_DELETE);
// Grant one edge type to user .
user->fine_grained_access_handler().edge_type_permissions().Grant("edgeTypeTest",
FineGrainedPermission::CREATE_DELETE);
// Check permissions.
ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest", FineGrainedPermission::READ),
PermissionLevel::GRANT);
ASSERT_EQ(
user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest", FineGrainedPermission::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());
// Check permissions.
ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest1", FineGrainedPermission::READ),
PermissionLevel::DENY);
ASSERT_EQ(
user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest1", FineGrainedPermission::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());
// Create role.
ASSERT_TRUE(auth.AddRole("admin"));
auto role = auth.GetRole("admin");
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", FineGrainedPermission::CREATE_DELETE);
role->fine_grained_access_handler().edge_type_permissions().Grant("roleEdgeTypeTest",
FineGrainedPermission::CREATE_DELETE);
user->SetRole(*role);
// Check permissions.
{
ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest", FineGrainedPermission::READ),
PermissionLevel::GRANT);
ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest", FineGrainedPermission::READ),
PermissionLevel::GRANT);
}
user->SetRole(*role);
// Check permissions.
{
ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest1", FineGrainedPermission::READ),
PermissionLevel::DENY);
ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest1", FineGrainedPermission::READ),
PermissionLevel::DENY);
}
}
#endif
TEST_F(AuthWithStorage, RoleManipulations) {
{
auto user1 = auth.AddUser("user1");
@ -407,6 +482,180 @@ TEST(AuthWithoutStorage, PermissionsMaskTest) {
ASSERT_EQ(p4.denies(), 2);
}
#ifdef MG_ENTERPRISE
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, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::READ), PermissionLevel::DENY);
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(any_label, FineGrainedPermission::CREATE_DELETE);
ASSERT_EQ(fga_permissions.GetGlobalPermission(), std::nullopt);
ASSERT_FALSE(fga_permissions.GetPermissions().empty());
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(asterisk, FineGrainedPermission::CREATE_DELETE);
ASSERT_EQ(fga_permissions.GetGlobalPermission(), kLabelPermissionAll);
ASSERT_TRUE(fga_permissions.GetPermissions().empty());
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(asterisk, FineGrainedPermission::CREATE_DELETE);
fga_permissions.Revoke(any_label);
ASSERT_EQ(fga_permissions.GetGlobalPermission(), kLabelPermissionAll);
ASSERT_TRUE(fga_permissions.GetPermissions().empty());
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(any_label, FineGrainedPermission::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, FineGrainedPermission::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, FineGrainedPermission::CREATE_DELETE);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(asterisk, FineGrainedPermission::UPDATE);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(asterisk, FineGrainedPermission::CREATE_DELETE);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(any_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions;
fga_permissions.Grant(asterisk, FineGrainedPermission::READ);
fga_permissions.Grant(check_label, FineGrainedPermission::UPDATE);
ASSERT_EQ(fga_permissions.Has(check_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions.Has(check_label, FineGrainedPermission::UPDATE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(check_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions.Has(non_check_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions.Has(non_check_label, FineGrainedPermission::UPDATE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions.Has(non_check_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
}
TEST_F(AuthWithStorage, FineGrainedAccessCheckerMerge) {
auto any_label = "AnyString";
auto check_label = "Label";
auto asterisk = "*";
{
FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
fga_permissions1.Grant(asterisk, FineGrainedPermission::READ);
auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
fga_permissions2.Grant(asterisk, FineGrainedPermission::READ);
auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
fga_permissions1.Grant(asterisk, FineGrainedPermission::READ);
fga_permissions2.Grant(asterisk, FineGrainedPermission::UPDATE);
auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::UPDATE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions3.Has(any_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
fga_permissions1.Grant(asterisk, FineGrainedPermission::READ);
fga_permissions1.Grant(check_label, FineGrainedPermission::UPDATE);
fga_permissions2.Grant(asterisk, FineGrainedPermission::UPDATE);
auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
ASSERT_EQ(fga_permissions3.Has(check_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(check_label, FineGrainedPermission::UPDATE), PermissionLevel::GRANT);
ASSERT_EQ(fga_permissions3.Has(check_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
{
FineGrainedAccessPermissions fga_permissions1, fga_permissions2;
fga_permissions1.Grant(asterisk, FineGrainedPermission::READ);
fga_permissions1.Grant(check_label, FineGrainedPermission::CREATE_DELETE);
fga_permissions2.Grant(asterisk, FineGrainedPermission::UPDATE);
fga_permissions2.Grant(check_label, FineGrainedPermission::READ);
auto fga_permissions3 = memgraph::auth::Merge(fga_permissions1, fga_permissions2);
ASSERT_EQ(fga_permissions3.Has(check_label, FineGrainedPermission::CREATE_DELETE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(check_label, FineGrainedPermission::UPDATE), PermissionLevel::DENY);
ASSERT_EQ(fga_permissions3.Has(check_label, FineGrainedPermission::READ), PermissionLevel::GRANT);
}
}
#endif
TEST(AuthWithoutStorage, UserSerializeDeserialize) {
auto user = User("test");
user.permissions().Grant(Permission::MATCH);
@ -468,8 +717,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto user1 = User("test", "pw", perms);
auto user2 = User("Test", "pw", perms);
auto fine_grained_access_handler = FineGrainedAccessHandler();
auto user1 = User("test", "pw", perms, fine_grained_access_handler);
auto user2 = User("Test", "pw", perms, fine_grained_access_handler);
ASSERT_EQ(user1, user2);
ASSERT_EQ(user1.username(), user2.username());
ASSERT_EQ(user1.username(), "test");
@ -485,8 +735,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto role1 = Role("role", perms);
auto role2 = Role("Role", perms);
auto fine_grained_access_handler = FineGrainedAccessHandler();
auto role1 = Role("role", perms, fine_grained_access_handler);
auto role2 = Role("Role", perms, fine_grained_access_handler);
ASSERT_EQ(role1, role2);
ASSERT_EQ(role1.rolename(), role2.rolename());
ASSERT_EQ(role1.rolename(), "role");

211
tests/unit/auth_checker.cpp Normal file
View File

@ -0,0 +1,211 @@
// 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 <gmock/gmock.h>
#include <gtest/gtest.h>
#include "auth/models.hpp"
#include "glue/auth_checker.hpp"
#include "query_plan_common.hpp"
#include "storage/v2/view.hpp"
#include "utils/license.hpp"
#ifdef MG_ENTERPRISE
class FineGrainedAuthCheckerFixture : public testing::Test {
protected:
memgraph::storage::Storage db;
memgraph::storage::Storage::Accessor storage_dba{db.Access()};
memgraph::query::DbAccessor dba{&storage_dba};
// make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2)
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
memgraph::query::VertexAccessor v3{dba.InsertVertex()};
memgraph::storage::EdgeTypeId edge_type_one{db.NameToEdgeType("edge_type_1")};
memgraph::storage::EdgeTypeId edge_type_two{db.NameToEdgeType("edge_type_2")};
memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type_one)};
memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&v1, &v3, edge_type_one)};
memgraph::query::EdgeAccessor r3{*dba.InsertEdge(&v1, &v2, edge_type_two)};
memgraph::query::EdgeAccessor r4{*dba.InsertEdge(&v1, &v3, edge_type_two)};
void SetUp() override {
memgraph::utils::license::global_license_checker.EnableTesting();
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l1")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l2")).HasValue());
ASSERT_TRUE(v3.AddLabel(dba.NameToLabel("l3")).HasValue());
dba.AdvanceCommand();
}
};
TEST_F(FineGrainedAuthCheckerFixture, GrantedAllLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(
auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantedAllEdgeTypes) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant(
"*", memgraph::auth::FineGrainedPermission::CREATE_DELETE);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(auth_checker.Has(r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(auth_checker.Has(r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(auth_checker.Has(r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, DeniedAllLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_FALSE(
auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, DeniedAllEdgeTypes) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_FALSE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(auth_checker.Has(r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(auth_checker.Has(r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(auth_checker.Has(r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantLabel) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l1",
memgraph::auth::FineGrainedPermission::CREATE_DELETE);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(
auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, DenyLabel) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_FALSE(
auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l1",
memgraph::auth::FineGrainedPermission::CREATE_DELETE);
user.fine_grained_access_handler().label_permissions().Grant("l2",
memgraph::auth::FineGrainedPermission::CREATE_DELETE);
user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(
auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v3, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v3, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, MultipleVertexLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l1",
memgraph::auth::FineGrainedPermission::CREATE_DELETE);
user.fine_grained_access_handler().label_permissions().Grant("l2",
memgraph::auth::FineGrainedPermission::CREATE_DELETE);
user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l3")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l1")).HasValue());
dba.AdvanceCommand();
ASSERT_FALSE(
auth_checker.Has(v1, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(
auth_checker.Has(v1, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v2, memgraph::storage::View::NEW, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(
auth_checker.Has(v2, memgraph::storage::View::OLD, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantEdgeType) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant(
"edge_type_1", memgraph::auth::FineGrainedPermission::CREATE_DELETE);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, DenyEdgeType) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1",
memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_FALSE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificEdgeTypes) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant(
"edge_type_1", memgraph::auth::FineGrainedPermission::CREATE_DELETE);
user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_2",
memgraph::auth::FineGrainedPermission::NOTHING);
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba};
ASSERT_TRUE(auth_checker.Has(r1, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_TRUE(auth_checker.Has(r2, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(auth_checker.Has(r3, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
ASSERT_FALSE(auth_checker.Has(r4, memgraph::query::AuthQuery::FineGrainedPrivilege::READ));
}
#endif

733
tests/unit/auth_handler.cpp Normal file
View File

@ -0,0 +1,733 @@
// 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{};
#ifdef MG_ENTERPRISE
memgraph::auth::FineGrainedAccessHandler handler{};
#endif
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{};
#ifdef MG_ENTERPRISE
handler = memgraph::auth::FineGrainedAccessHandler{};
#endif
}
};
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};
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};
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};
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};
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};
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};
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};
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};
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};
auth->SaveRole(role);
memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
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};
auth->SaveRole(role);
memgraph::auth::User user = memgraph::auth::User{user_name, "", perms};
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};
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,
};
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};
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};
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");
}
#ifdef MG_ENTERPRISE
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.Grant(label_repr, memgraph::auth::FineGrainedPermission::NOTHING);
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(), "NOTHING");
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.Grant(edge_type_repr, memgraph::auth::FineGrainedPermission::NOTHING);
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(), "NOTHING");
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.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");
}
#endif

View File

@ -13,6 +13,8 @@
#include "gtest/gtest.h"
#include "auth/models.hpp"
#include "glue/auth_checker.hpp"
#include "query/context.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/interpret/frame.hpp"
@ -189,6 +191,15 @@ std::vector<std::vector<memgraph::query::TypedValue>> PullResults(memgraph::quer
enum class FilterLambdaType { NONE, USE_FRAME, USE_FRAME_NULL, USE_CTX, ERROR };
enum class FineGrainedTestType {
ALL_GRANTED,
ALL_DENIED,
EDGE_TYPE_A_DENIED,
EDGE_TYPE_B_DENIED,
LABEL_0_DENIED,
LABEL_3_DENIED
};
// Common interface for single-node and distributed Memgraph.
class Database {
public:
@ -202,7 +213,6 @@ class Database {
virtual std::pair<std::vector<memgraph::query::VertexAccessor>, std::vector<memgraph::query::EdgeAccessor>>
BuildGraph(memgraph::query::DbAccessor *dba, const std::vector<int> &vertex_locations,
const std::vector<std::tuple<int, int, std::string>> &edges) = 0;
virtual ~Database() {}
};
@ -440,3 +450,272 @@ void BfsTest(Database *db, int lower_bound, int upper_bound, memgraph::query::Ed
dba.Abort();
}
#ifdef MG_ENTERPRISE
void BfsTestWithFineGrainedFiltering(Database *db, int lower_bound, int upper_bound,
memgraph::query::EdgeAtom::Direction direction,
std::vector<std::string> edge_types, bool known_sink,
FineGrainedTestType fine_grained_test_type) {
auto storage_dba = db->Access();
memgraph::query::DbAccessor db_accessor(&storage_dba);
memgraph::query::AstStorage storage;
memgraph::query::ExecutionContext context{&db_accessor};
memgraph::query::Symbol blocked_symbol = context.symbol_table.CreateSymbol("blocked", true);
memgraph::query::Symbol source_symbol = context.symbol_table.CreateSymbol("source", true);
memgraph::query::Symbol sink_symbol = context.symbol_table.CreateSymbol("sink", true);
memgraph::query::Symbol edges_symbol = context.symbol_table.CreateSymbol("edges", true);
memgraph::query::Symbol inner_node_symbol = context.symbol_table.CreateSymbol("inner_node", true);
memgraph::query::Symbol inner_edge_symbol = context.symbol_table.CreateSymbol("inner_edge", true);
std::vector<memgraph::query::VertexAccessor> vertices;
std::vector<memgraph::query::EdgeAccessor> edges;
std::tie(vertices, edges) = db->BuildGraph(&db_accessor, kVertexLocations, kEdges);
db_accessor.AdvanceCommand();
std::shared_ptr<memgraph::query::plan::LogicalOperator> input_operator;
memgraph::query::Expression *filter_expr = nullptr;
input_operator =
std::make_shared<Yield>(nullptr, std::vector<memgraph::query::Symbol>{blocked_symbol},
std::vector<std::vector<memgraph::query::TypedValue>>{{memgraph::query::TypedValue()}});
memgraph::auth::User user{"test"};
std::vector<std::pair<int, int>> edges_in_result;
switch (fine_grained_test_type) {
case FineGrainedTestType::ALL_GRANTED:
user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().edge_type_permissions().Grant("*",
memgraph::auth::FineGrainedPermission::READ);
edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"});
break;
case FineGrainedTestType::ALL_DENIED:
break;
case FineGrainedTestType::EDGE_TYPE_A_DENIED:
user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().edge_type_permissions().Grant("b",
memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().edge_type_permissions().Grant("a",
memgraph::auth::FineGrainedPermission::NOTHING);
edges_in_result = GetEdgeList(kEdges, direction, {"b"});
break;
case FineGrainedTestType::EDGE_TYPE_B_DENIED:
user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().edge_type_permissions().Grant("a",
memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().edge_type_permissions().Grant("b",
memgraph::auth::FineGrainedPermission::NOTHING);
edges_in_result = GetEdgeList(kEdges, direction, {"a"});
break;
case FineGrainedTestType::LABEL_0_DENIED:
user.fine_grained_access_handler().edge_type_permissions().Grant("*",
memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("3", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("4", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("0", memgraph::auth::FineGrainedPermission::NOTHING);
edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"});
edges_in_result.erase(
std::remove_if(edges_in_result.begin(), edges_in_result.end(), [](const auto &e) { return e.second == 0; }),
edges_in_result.end());
break;
case FineGrainedTestType::LABEL_3_DENIED:
user.fine_grained_access_handler().edge_type_permissions().Grant("*",
memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("0", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("1", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("2", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("4", memgraph::auth::FineGrainedPermission::READ);
user.fine_grained_access_handler().label_permissions().Grant("3", memgraph::auth::FineGrainedPermission::NOTHING);
edges_in_result = GetEdgeList(kEdges, direction, {"a", "b"});
edges_in_result.erase(
std::remove_if(edges_in_result.begin(), edges_in_result.end(), [](const auto &e) { return e.second == 3; }),
edges_in_result.end());
break;
}
memgraph::glue::FineGrainedAuthChecker auth_checker{user, &db_accessor};
context.auth_checker = std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(auth_checker));
// We run BFS once from each vertex for each blocked entity.
input_operator = YieldVertices(&db_accessor, vertices, source_symbol, input_operator);
// If the sink is known, we run BFS for all posible combinations of source,
// sink and blocked entity.
if (known_sink) {
input_operator = YieldVertices(&db_accessor, vertices, sink_symbol, input_operator);
}
std::vector<memgraph::storage::EdgeTypeId> storage_edge_types;
for (const auto &t : edge_types) {
storage_edge_types.push_back(db_accessor.NameToEdgeType(t));
}
input_operator = db->MakeBfsOperator(
source_symbol, sink_symbol, edges_symbol, direction, storage_edge_types, input_operator, known_sink,
lower_bound == -1 ? nullptr : LITERAL(lower_bound), upper_bound == -1 ? nullptr : LITERAL(upper_bound),
memgraph::query::plan::ExpansionLambda{inner_edge_symbol, inner_node_symbol, filter_expr});
context.evaluation_context.properties = memgraph::query::NamesToProperties(storage.properties_, &db_accessor);
context.evaluation_context.labels = memgraph::query::NamesToLabels(storage.labels_, &db_accessor);
std::vector<std::vector<memgraph::query::TypedValue>> results;
results = PullResults(input_operator.get(), &context,
std::vector<memgraph::query::Symbol>{source_symbol, sink_symbol, edges_symbol, blocked_symbol});
switch (fine_grained_test_type) {
case FineGrainedTestType::ALL_GRANTED:
switch (direction) {
case memgraph::query::EdgeAtom::Direction::IN:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
case memgraph::query::EdgeAtom::Direction::OUT:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
case memgraph::query::EdgeAtom::Direction::BOTH:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
}
break;
case FineGrainedTestType::ALL_DENIED:
switch (direction) {
case memgraph::query::EdgeAtom::Direction::IN:
EXPECT_EQ(results.size(), 0);
break;
case memgraph::query::EdgeAtom::Direction::OUT:
EXPECT_EQ(results.size(), 0);
break;
case memgraph::query::EdgeAtom::Direction::BOTH:
EXPECT_EQ(results.size(), 0);
break;
}
break;
case FineGrainedTestType::EDGE_TYPE_A_DENIED:
switch (direction) {
case memgraph::query::EdgeAtom::Direction::IN:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
case memgraph::query::EdgeAtom::Direction::OUT:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
case memgraph::query::EdgeAtom::Direction::BOTH:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
}
break;
case FineGrainedTestType::EDGE_TYPE_B_DENIED:
switch (direction) {
case memgraph::query::EdgeAtom::Direction::IN:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
case memgraph::query::EdgeAtom::Direction::OUT:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
case memgraph::query::EdgeAtom::Direction::BOTH:
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
break;
}
break;
case FineGrainedTestType::LABEL_0_DENIED:
switch (direction) {
case memgraph::query::EdgeAtom::Direction::IN:
if (known_sink) {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
} else {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
}
break;
case memgraph::query::EdgeAtom::Direction::OUT:
if (known_sink) {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
} else {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
}
break;
case memgraph::query::EdgeAtom::Direction::BOTH:
if (known_sink) {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
} else {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
}
break;
}
break;
case FineGrainedTestType::LABEL_3_DENIED:
switch (direction) {
case memgraph::query::EdgeAtom::Direction::IN:
if (known_sink) {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
} else {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
}
break;
case memgraph::query::EdgeAtom::Direction::OUT:
if (known_sink) {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
} else {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
}
break;
case memgraph::query::EdgeAtom::Direction::BOTH:
if (known_sink) {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
} else {
CheckPathsAndExtractDistances(
&db_accessor, edges_in_result,
std::vector<std::vector<memgraph::query::TypedValue>>(results.begin(), results.begin()));
}
break;
}
break;
}
db_accessor.Abort();
}
#endif

View File

@ -0,0 +1,115 @@
// 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 "bfs_common.hpp"
#include <optional>
#include <string>
#include <unordered_map>
#include <gtest/internal/gtest-param-util-generated.h>
#include "auth/models.hpp"
#include "utils/license.hpp"
using namespace memgraph::query;
using namespace memgraph::query::plan;
class VertexDb : public Database {
public:
VertexDb() : db_() {}
memgraph::storage::Storage::Accessor Access() override { return db_.Access(); }
std::unique_ptr<LogicalOperator> MakeBfsOperator(Symbol source_sym, Symbol sink_sym, Symbol edge_sym,
EdgeAtom::Direction direction,
const std::vector<memgraph::storage::EdgeTypeId> &edge_types,
const std::shared_ptr<LogicalOperator> &input, bool existing_node,
Expression *lower_bound, Expression *upper_bound,
const ExpansionLambda &filter_lambda) override {
return std::make_unique<ExpandVariable>(input, source_sym, sink_sym, edge_sym, EdgeAtom::Type::BREADTH_FIRST,
direction, edge_types, false, lower_bound, upper_bound, existing_node,
filter_lambda, std::nullopt, std::nullopt);
}
std::pair<std::vector<memgraph::query::VertexAccessor>, std::vector<memgraph::query::EdgeAccessor>> BuildGraph(
memgraph::query::DbAccessor *dba, const std::vector<int> &vertex_locations,
const std::vector<std::tuple<int, int, std::string>> &edges) override {
std::vector<memgraph::query::VertexAccessor> vertex_addr;
std::vector<memgraph::query::EdgeAccessor> edge_addr;
for (size_t id = 0; id < vertex_locations.size(); ++id) {
auto vertex = dba->InsertVertex();
MG_ASSERT(
vertex.SetProperty(dba->NameToProperty("id"), memgraph::storage::PropertyValue(static_cast<int64_t>(id)))
.HasValue());
MG_ASSERT(vertex.AddLabel(dba->NameToLabel(std::to_string(id))).HasValue());
vertex_addr.push_back(vertex);
}
for (auto e : edges) {
int u, v;
std::string type;
std::tie(u, v, type) = e;
auto &from = vertex_addr[u];
auto &to = vertex_addr[v];
auto edge = dba->InsertEdge(&from, &to, dba->NameToEdgeType(type));
MG_ASSERT(edge->SetProperty(dba->NameToProperty("from"), memgraph::storage::PropertyValue(u)).HasValue());
MG_ASSERT(edge->SetProperty(dba->NameToProperty("to"), memgraph::storage::PropertyValue(v)).HasValue());
edge_addr.push_back(*edge);
}
return std::make_pair(vertex_addr, edge_addr);
}
protected:
memgraph::storage::Storage db_;
};
#ifdef MG_ENTERPRISE
class FineGrainedBfsTest
: public ::testing::TestWithParam<
std::tuple<int, int, EdgeAtom::Direction, std::vector<std::string>, bool, FineGrainedTestType>> {
public:
static void SetUpTestCase() {
memgraph::utils::license::global_license_checker.EnableTesting();
db_ = std::make_unique<VertexDb>();
}
static void TearDownTestCase() { db_ = nullptr; }
protected:
static std::unique_ptr<VertexDb> db_;
};
TEST_P(FineGrainedBfsTest, All) {
int lower_bound;
int upper_bound;
EdgeAtom::Direction direction;
std::vector<std::string> edge_types;
bool known_sink;
FineGrainedTestType fine_grained_test_type;
std::tie(lower_bound, upper_bound, direction, edge_types, known_sink, fine_grained_test_type) = GetParam();
BfsTestWithFineGrainedFiltering(db_.get(), lower_bound, upper_bound, direction, edge_types, known_sink,
fine_grained_test_type);
}
std::unique_ptr<VertexDb> FineGrainedBfsTest::db_{nullptr};
INSTANTIATE_TEST_CASE_P(
FineGrained, FineGrainedBfsTest,
testing::Combine(testing::Values(3), testing::Values(-1),
testing::Values(EdgeAtom::Direction::OUT, EdgeAtom::Direction::IN, EdgeAtom::Direction::BOTH),
testing::Values(std::vector<std::string>{}), testing::Bool(),
testing::Values(FineGrainedTestType::ALL_GRANTED, FineGrainedTestType::ALL_DENIED,
FineGrainedTestType::EDGE_TYPE_A_DENIED, FineGrainedTestType::EDGE_TYPE_B_DENIED,
FineGrainedTestType::LABEL_0_DENIED, FineGrainedTestType::LABEL_3_DENIED)));
#endif

View File

@ -2056,9 +2056,11 @@ TEST_P(CypherMainVisitorTest, UnionAll) {
ASSERT_FALSE(return_clause->body_.distinct);
}
void check_auth_query(Base *ast_generator, std::string input, AuthQuery::Action action, std::string user,
std::string role, std::string user_or_role, std::optional<TypedValue> password,
std::vector<AuthQuery::Privilege> privileges) {
void check_auth_query(
Base *ast_generator, std::string input, AuthQuery::Action action, std::string user, std::string role,
std::string user_or_role, std::optional<TypedValue> password, std::vector<AuthQuery::Privilege> privileges,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges,
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges) {
auto *auth_query = dynamic_cast<AuthQuery *>(ast_generator->ParseQuery(input));
ASSERT_TRUE(auth_query);
EXPECT_EQ(auth_query->action_, action);
@ -2070,48 +2072,55 @@ void check_auth_query(Base *ast_generator, std::string input, AuthQuery::Action
ast_generator->CheckLiteral(auth_query->password_, *password);
}
EXPECT_EQ(auth_query->privileges_, privileges);
EXPECT_EQ(auth_query->label_privileges_, label_privileges);
EXPECT_EQ(auth_query->edge_type_privileges_, edge_type_privileges);
}
TEST_P(CypherMainVisitorTest, UserOrRoleName) {
auto &ast_generator = *GetParam();
check_auth_query(&ast_generator, "CREATE ROLE `user`", AuthQuery::Action::CREATE_ROLE, "", "user", "", {}, {});
check_auth_query(&ast_generator, "CREATE ROLE us___er", AuthQuery::Action::CREATE_ROLE, "", "us___er", "", {}, {});
check_auth_query(&ast_generator, "CREATE ROLE `us+er`", AuthQuery::Action::CREATE_ROLE, "", "us+er", "", {}, {});
check_auth_query(&ast_generator, "CREATE ROLE `us|er`", AuthQuery::Action::CREATE_ROLE, "", "us|er", "", {}, {});
check_auth_query(&ast_generator, "CREATE ROLE `us er`", AuthQuery::Action::CREATE_ROLE, "", "us er", "", {}, {});
check_auth_query(&ast_generator, "CREATE ROLE `user`", AuthQuery::Action::CREATE_ROLE, "", "user", "", {}, {}, {},
{});
check_auth_query(&ast_generator, "CREATE ROLE us___er", AuthQuery::Action::CREATE_ROLE, "", "us___er", "", {}, {}, {},
{});
check_auth_query(&ast_generator, "CREATE ROLE `us+er`", AuthQuery::Action::CREATE_ROLE, "", "us+er", "", {}, {}, {},
{});
check_auth_query(&ast_generator, "CREATE ROLE `us|er`", AuthQuery::Action::CREATE_ROLE, "", "us|er", "", {}, {}, {},
{});
check_auth_query(&ast_generator, "CREATE ROLE `us er`", AuthQuery::Action::CREATE_ROLE, "", "us er", "", {}, {}, {},
{});
}
TEST_P(CypherMainVisitorTest, CreateRole) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("CREATE ROLE"), SyntaxException);
check_auth_query(&ast_generator, "CREATE ROLE rola", AuthQuery::Action::CREATE_ROLE, "", "rola", "", {}, {});
check_auth_query(&ast_generator, "CREATE ROLE rola", AuthQuery::Action::CREATE_ROLE, "", "rola", "", {}, {}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("CREATE ROLE lagano rolamo"), SyntaxException);
}
TEST_P(CypherMainVisitorTest, DropRole) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("DROP ROLE"), SyntaxException);
check_auth_query(&ast_generator, "DROP ROLE rola", AuthQuery::Action::DROP_ROLE, "", "rola", "", {}, {});
check_auth_query(&ast_generator, "DROP ROLE rola", AuthQuery::Action::DROP_ROLE, "", "rola", "", {}, {}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("DROP ROLE lagano rolamo"), SyntaxException);
}
TEST_P(CypherMainVisitorTest, ShowRoles) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("SHOW ROLES ROLES"), SyntaxException);
check_auth_query(&ast_generator, "SHOW ROLES", AuthQuery::Action::SHOW_ROLES, "", "", "", {}, {});
check_auth_query(&ast_generator, "SHOW ROLES", AuthQuery::Action::SHOW_ROLES, "", "", "", {}, {}, {}, {});
}
TEST_P(CypherMainVisitorTest, CreateUser) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("CREATE USER"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("CREATE USER 123"), SyntaxException);
check_auth_query(&ast_generator, "CREATE USER user", AuthQuery::Action::CREATE_USER, "user", "", "", {}, {});
check_auth_query(&ast_generator, "CREATE USER user", AuthQuery::Action::CREATE_USER, "user", "", "", {}, {}, {}, {});
check_auth_query(&ast_generator, "CREATE USER user IDENTIFIED BY 'password'", AuthQuery::Action::CREATE_USER, "user",
"", "", TypedValue("password"), {});
"", "", TypedValue("password"), {}, {}, {});
check_auth_query(&ast_generator, "CREATE USER user IDENTIFIED BY ''", AuthQuery::Action::CREATE_USER, "user", "", "",
TypedValue(""), {});
TypedValue(""), {}, {}, {});
check_auth_query(&ast_generator, "CREATE USER user IDENTIFIED BY null", AuthQuery::Action::CREATE_USER, "user", "",
"", TypedValue(), {});
"", TypedValue(), {}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("CRATE USER user IDENTIFIED BY password"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("CREATE USER user IDENTIFIED BY 5"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("CREATE USER user IDENTIFIED BY "), SyntaxException);
@ -2122,23 +2131,23 @@ TEST_P(CypherMainVisitorTest, SetPassword) {
ASSERT_THROW(ast_generator.ParseQuery("SET PASSWORD FOR"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("SET PASSWORD FOR user "), SyntaxException);
check_auth_query(&ast_generator, "SET PASSWORD FOR user TO null", AuthQuery::Action::SET_PASSWORD, "user", "", "",
TypedValue(), {});
TypedValue(), {}, {}, {});
check_auth_query(&ast_generator, "SET PASSWORD FOR user TO 'password'", AuthQuery::Action::SET_PASSWORD, "user", "",
"", TypedValue("password"), {});
"", TypedValue("password"), {}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("SET PASSWORD FOR user To 5"), SyntaxException);
}
TEST_P(CypherMainVisitorTest, DropUser) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("DROP USER"), SyntaxException);
check_auth_query(&ast_generator, "DROP USER user", AuthQuery::Action::DROP_USER, "user", "", "", {}, {});
check_auth_query(&ast_generator, "DROP USER user", AuthQuery::Action::DROP_USER, "user", "", "", {}, {}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("DROP USER lagano rolamo"), SyntaxException);
}
TEST_P(CypherMainVisitorTest, ShowUsers) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("SHOW USERS ROLES"), SyntaxException);
check_auth_query(&ast_generator, "SHOW USERS", AuthQuery::Action::SHOW_USERS, "", "", "", {}, {});
check_auth_query(&ast_generator, "SHOW USERS", AuthQuery::Action::SHOW_USERS, "", "", "", {}, {}, {}, {});
}
TEST_P(CypherMainVisitorTest, SetRole) {
@ -2147,10 +2156,10 @@ TEST_P(CypherMainVisitorTest, SetRole) {
ASSERT_THROW(ast_generator.ParseQuery("SET ROLE user"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("SET ROLE FOR user"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("SET ROLE FOR user TO"), SyntaxException);
check_auth_query(&ast_generator, "SET ROLE FOR user TO role", AuthQuery::Action::SET_ROLE, "user", "role", "", {},
{});
check_auth_query(&ast_generator, "SET ROLE FOR user TO null", AuthQuery::Action::SET_ROLE, "user", "null", "", {},
{});
check_auth_query(&ast_generator, "SET ROLE FOR user TO role", AuthQuery::Action::SET_ROLE, "user", "role", "", {}, {},
{}, {});
check_auth_query(&ast_generator, "SET ROLE FOR user TO null", AuthQuery::Action::SET_ROLE, "user", "null", "", {}, {},
{}, {});
}
TEST_P(CypherMainVisitorTest, ClearRole) {
@ -2158,7 +2167,8 @@ TEST_P(CypherMainVisitorTest, ClearRole) {
ASSERT_THROW(ast_generator.ParseQuery("CLEAR ROLE"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("CLEAR ROLE user"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("CLEAR ROLE FOR user TO"), SyntaxException);
check_auth_query(&ast_generator, "CLEAR ROLE FOR user", AuthQuery::Action::CLEAR_ROLE, "user", "", "", {}, {});
check_auth_query(&ast_generator, "CLEAR ROLE FOR user", AuthQuery::Action::CLEAR_ROLE, "user", "", "", {}, {}, {},
{});
}
TEST_P(CypherMainVisitorTest, GrantPrivilege) {
@ -2169,50 +2179,97 @@ TEST_P(CypherMainVisitorTest, GrantPrivilege) {
ASSERT_THROW(ast_generator.ParseQuery("GRANT MATCH, TO user"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("GRANT MATCH, BLABLA TO user"), SyntaxException);
check_auth_query(&ast_generator, "GRANT MATCH TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MATCH});
{AuthQuery::Privilege::MATCH}, {}, {});
check_auth_query(&ast_generator, "GRANT MATCH, AUTH TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH});
{AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH}, {}, {});
// Verify that all privileges are correctly visited.
check_auth_query(&ast_generator, "GRANT CREATE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::CREATE});
{AuthQuery::Privilege::CREATE}, {}, {});
check_auth_query(&ast_generator, "GRANT DELETE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DELETE});
{AuthQuery::Privilege::DELETE}, {}, {});
check_auth_query(&ast_generator, "GRANT MERGE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MERGE});
{AuthQuery::Privilege::MERGE}, {}, {});
check_auth_query(&ast_generator, "GRANT SET TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::SET});
{AuthQuery::Privilege::SET}, {}, {});
check_auth_query(&ast_generator, "GRANT REMOVE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::REMOVE});
{AuthQuery::Privilege::REMOVE}, {}, {});
check_auth_query(&ast_generator, "GRANT INDEX TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::INDEX});
{AuthQuery::Privilege::INDEX}, {}, {});
check_auth_query(&ast_generator, "GRANT STATS TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::STATS});
{AuthQuery::Privilege::STATS}, {}, {});
check_auth_query(&ast_generator, "GRANT AUTH TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::AUTH});
{AuthQuery::Privilege::AUTH}, {}, {});
check_auth_query(&ast_generator, "GRANT CONSTRAINT TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::CONSTRAINT});
{AuthQuery::Privilege::CONSTRAINT}, {}, {});
check_auth_query(&ast_generator, "GRANT DUMP TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DUMP});
{AuthQuery::Privilege::DUMP}, {}, {});
check_auth_query(&ast_generator, "GRANT REPLICATION TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::REPLICATION});
{AuthQuery::Privilege::REPLICATION}, {}, {});
check_auth_query(&ast_generator, "GRANT DURABILITY TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DURABILITY});
{AuthQuery::Privilege::DURABILITY}, {}, {});
check_auth_query(&ast_generator, "GRANT READ_FILE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::READ_FILE});
{AuthQuery::Privilege::READ_FILE}, {}, {});
check_auth_query(&ast_generator, "GRANT FREE_MEMORY TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::FREE_MEMORY});
{AuthQuery::Privilege::FREE_MEMORY}, {}, {});
check_auth_query(&ast_generator, "GRANT TRIGGER TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::TRIGGER});
{AuthQuery::Privilege::TRIGGER}, {}, {});
check_auth_query(&ast_generator, "GRANT CONFIG TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::CONFIG});
{AuthQuery::Privilege::CONFIG}, {}, {});
check_auth_query(&ast_generator, "GRANT STREAM TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::STREAM});
{AuthQuery::Privilege::STREAM}, {}, {});
check_auth_query(&ast_generator, "GRANT WEBSOCKET TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::WEBSOCKET});
{AuthQuery::Privilege::WEBSOCKET}, {}, {});
check_auth_query(&ast_generator, "GRANT MODULE_READ TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MODULE_READ});
{AuthQuery::Privilege::MODULE_READ}, {}, {});
check_auth_query(&ast_generator, "GRANT MODULE_WRITE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MODULE_WRITE});
{AuthQuery::Privilege::MODULE_WRITE}, {}, {});
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges{};
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges{};
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::READ}, {{"*"}}}});
check_auth_query(&ast_generator, "GRANT READ ON LABELS * TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user",
{}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::UPDATE}, {{"*"}}}});
check_auth_query(&ast_generator, "GRANT UPDATE ON LABELS * TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "",
"user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE}, {{"*"}}}});
check_auth_query(&ast_generator, "GRANT CREATE_DELETE ON LABELS * TO user", AuthQuery::Action::GRANT_PRIVILEGE, "",
"", "user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::READ}, {{"Label1"}, {"Label2"}}}});
check_auth_query(&ast_generator, "GRANT READ ON LABELS :Label1, :Label2 TO user", AuthQuery::Action::GRANT_PRIVILEGE,
"", "", "user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::UPDATE}, {{"Label1"}, {"Label2"}}}});
check_auth_query(&ast_generator, "GRANT UPDATE ON LABELS :Label1, :Label2 TO user",
AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE}, {{"Label1"}, {"Label2"}}}});
check_auth_query(&ast_generator, "GRANT CREATE_DELETE ON LABELS :Label1, :Label2 TO user",
AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::READ}, {{"Label1"}, {"Label2"}}},
{{AuthQuery::FineGrainedPrivilege::UPDATE}, {{"Label3"}}}});
check_auth_query(&ast_generator, "GRANT READ ON LABELS :Label1, :Label2, UPDATE ON LABELS :Label3 TO user",
AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::READ}, {{"Label1"}, {"Label2"}}}});
edge_type_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::READ}, {{"Edge1"}, {"Edge2"}, {"Edge3"}}}});
check_auth_query(&ast_generator,
"GRANT READ ON LABELS :Label1, :Label2, READ ON EDGE_TYPES :Edge1, :Edge2, :Edge3 TO user",
AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, {}, label_privileges, edge_type_privileges);
label_privileges.clear();
edge_type_privileges.clear();
}
TEST_P(CypherMainVisitorTest, DenyPrivilege) {
@ -2223,36 +2280,36 @@ TEST_P(CypherMainVisitorTest, DenyPrivilege) {
ASSERT_THROW(ast_generator.ParseQuery("DENY MATCH, TO user"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("DENY MATCH, BLABLA TO user"), SyntaxException);
check_auth_query(&ast_generator, "DENY MATCH TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MATCH});
{AuthQuery::Privilege::MATCH}, {}, {});
check_auth_query(&ast_generator, "DENY MATCH, AUTH TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH});
{AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH}, {}, {});
// Verify that all privileges are correctly visited.
check_auth_query(&ast_generator, "DENY CREATE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::CREATE});
{AuthQuery::Privilege::CREATE}, {}, {});
check_auth_query(&ast_generator, "DENY DELETE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DELETE});
{AuthQuery::Privilege::DELETE}, {}, {});
check_auth_query(&ast_generator, "DENY MERGE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MERGE});
{AuthQuery::Privilege::MERGE}, {}, {});
check_auth_query(&ast_generator, "DENY SET TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::SET});
{AuthQuery::Privilege::SET}, {}, {});
check_auth_query(&ast_generator, "DENY REMOVE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::REMOVE});
{AuthQuery::Privilege::REMOVE}, {}, {});
check_auth_query(&ast_generator, "DENY INDEX TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::INDEX});
{AuthQuery::Privilege::INDEX}, {}, {});
check_auth_query(&ast_generator, "DENY STATS TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::STATS});
{AuthQuery::Privilege::STATS}, {}, {});
check_auth_query(&ast_generator, "DENY AUTH TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::AUTH});
{AuthQuery::Privilege::AUTH}, {}, {});
check_auth_query(&ast_generator, "DENY CONSTRAINT TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::CONSTRAINT});
{AuthQuery::Privilege::CONSTRAINT}, {}, {});
check_auth_query(&ast_generator, "DENY DUMP TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DUMP});
{AuthQuery::Privilege::DUMP}, {}, {});
check_auth_query(&ast_generator, "DENY WEBSOCKET TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::WEBSOCKET});
{AuthQuery::Privilege::WEBSOCKET}, {}, {});
check_auth_query(&ast_generator, "DENY MODULE_READ TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MODULE_READ});
{AuthQuery::Privilege::MODULE_READ}, {}, {});
check_auth_query(&ast_generator, "DENY MODULE_WRITE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MODULE_WRITE});
{AuthQuery::Privilege::MODULE_WRITE}, {}, {});
}
TEST_P(CypherMainVisitorTest, RevokePrivilege) {
@ -2263,52 +2320,75 @@ TEST_P(CypherMainVisitorTest, RevokePrivilege) {
ASSERT_THROW(ast_generator.ParseQuery("REVOKE MATCH, FROM user"), SyntaxException);
ASSERT_THROW(ast_generator.ParseQuery("REVOKE MATCH, BLABLA FROM user"), SyntaxException);
check_auth_query(&ast_generator, "REVOKE MATCH FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MATCH});
{AuthQuery::Privilege::MATCH}, {}, {});
check_auth_query(&ast_generator, "REVOKE MATCH, AUTH FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
{}, {AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH});
{}, {AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH}, {}, {});
check_auth_query(&ast_generator, "REVOKE ALL PRIVILEGES FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "",
"user", {}, kPrivilegesAll);
"user", {}, kPrivilegesAll, {}, {});
// Verify that all privileges are correctly visited.
check_auth_query(&ast_generator, "REVOKE CREATE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::CREATE});
{AuthQuery::Privilege::CREATE}, {}, {});
check_auth_query(&ast_generator, "REVOKE DELETE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DELETE});
{AuthQuery::Privilege::DELETE}, {}, {});
check_auth_query(&ast_generator, "REVOKE MERGE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::MERGE});
{AuthQuery::Privilege::MERGE}, {}, {});
check_auth_query(&ast_generator, "REVOKE SET FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::SET});
{AuthQuery::Privilege::SET}, {}, {});
check_auth_query(&ast_generator, "REVOKE REMOVE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::REMOVE});
{AuthQuery::Privilege::REMOVE}, {}, {});
check_auth_query(&ast_generator, "REVOKE INDEX FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::INDEX});
{AuthQuery::Privilege::INDEX}, {}, {});
check_auth_query(&ast_generator, "REVOKE STATS FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::STATS});
{AuthQuery::Privilege::STATS}, {}, {});
check_auth_query(&ast_generator, "REVOKE AUTH FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::AUTH});
{AuthQuery::Privilege::AUTH}, {}, {});
check_auth_query(&ast_generator, "REVOKE CONSTRAINT FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
{}, {AuthQuery::Privilege::CONSTRAINT});
{}, {AuthQuery::Privilege::CONSTRAINT}, {}, {});
check_auth_query(&ast_generator, "REVOKE DUMP FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{AuthQuery::Privilege::DUMP});
{AuthQuery::Privilege::DUMP}, {}, {});
check_auth_query(&ast_generator, "REVOKE WEBSOCKET FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
{}, {AuthQuery::Privilege::WEBSOCKET});
{}, {AuthQuery::Privilege::WEBSOCKET}, {}, {});
check_auth_query(&ast_generator, "REVOKE MODULE_READ FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
{}, {AuthQuery::Privilege::MODULE_READ});
{}, {AuthQuery::Privilege::MODULE_READ}, {}, {});
check_auth_query(&ast_generator, "REVOKE MODULE_WRITE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
{}, {AuthQuery::Privilege::MODULE_WRITE});
{}, {AuthQuery::Privilege::MODULE_WRITE}, {}, {});
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> label_privileges{};
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>> edge_type_privileges{};
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE}, {{"*"}}}});
check_auth_query(&ast_generator, "REVOKE LABELS * FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
{}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE}, {{"Label1"}, {"Label2"}}}});
check_auth_query(&ast_generator, "REVOKE LABELS :Label1, :Label2 FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "",
"", "user", {}, {}, label_privileges, {});
label_privileges.clear();
label_privileges.push_back({{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE}, {{"Label1"}, {"Label2"}}}});
edge_type_privileges.push_back(
{{{AuthQuery::FineGrainedPrivilege::CREATE_DELETE}, {{"Edge1"}, {"Edge2"}, {"Edge3"}}}});
check_auth_query(&ast_generator, "REVOKE LABELS :Label1, :Label2, EDGE_TYPES :Edge1, :Edge2, :Edge3 FROM user",
AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {}, {}, label_privileges, edge_type_privileges);
label_privileges.clear();
edge_type_privileges.clear();
}
TEST_P(CypherMainVisitorTest, ShowPrivileges) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("SHOW PRIVILEGES FOR"), SyntaxException);
check_auth_query(&ast_generator, "SHOW PRIVILEGES FOR user", AuthQuery::Action::SHOW_PRIVILEGES, "", "", "user", {},
{});
{}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("SHOW PRIVILEGES FOR user1, user2"), SyntaxException);
}
TEST_P(CypherMainVisitorTest, ShowRoleForUser) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("SHOW ROLE FOR "), SyntaxException);
check_auth_query(&ast_generator, "SHOW ROLE FOR user", AuthQuery::Action::SHOW_ROLE_FOR_USER, "user", "", "", {}, {});
check_auth_query(&ast_generator, "SHOW ROLE FOR user", AuthQuery::Action::SHOW_ROLE_FOR_USER, "user", "", "", {}, {},
{}, {});
ASSERT_THROW(ast_generator.ParseQuery("SHOW ROLE FOR user1, user2"), SyntaxException);
}
@ -2316,7 +2396,7 @@ TEST_P(CypherMainVisitorTest, ShowUsersForRole) {
auto &ast_generator = *GetParam();
ASSERT_THROW(ast_generator.ParseQuery("SHOW USERS FOR "), SyntaxException);
check_auth_query(&ast_generator, "SHOW USERS FOR role", AuthQuery::Action::SHOW_USERS_FOR_ROLE, "", "role", "", {},
{});
{}, {}, {});
ASSERT_THROW(ast_generator.ParseQuery("SHOW USERS FOR role1, role2"), SyntaxException);
}

View File

@ -531,9 +531,9 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
memgraph::query::test_common::OnCreate { \
std::vector<memgraph::query::Clause *> { __VA_ARGS__ } \
}
#define CREATE_INDEX_ON(label, property) \
#define CREATE_INDEX_ON(label, property) \
storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, (label), \
std::vector<memgraph::query::PropertyIx>{(property)})
std::vector<memgraph::query::PropertyIx>{(property)})
#define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__)
#define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
#define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__)
@ -583,7 +583,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
#define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__})
#define EXTRACT(variable, list, expr) \
storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr)
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges))
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \
(labels), (edgeTypes))
#define DROP_USER(usernames) storage.Create<memgraph::query::DropUser>((usernames))
#define CALL_PROCEDURE(...) memgraph::query::test_common::GetCallProcedure(storage, __VA_ARGS__)

View File

@ -15,6 +15,8 @@
#include <memory>
#include <vector>
#include "auth/models.hpp"
#include "glue/auth_checker.hpp"
#include "query/common.hpp"
#include "query/context.hpp"
#include "query/db_accessor.hpp"
@ -39,6 +41,19 @@ ExecutionContext MakeContext(const AstStorage &storage, const SymbolTable &symbo
context.evaluation_context.labels = NamesToLabels(storage.labels_, dba);
return context;
}
#ifdef MG_ENTERPRISE
ExecutionContext MakeContextWithFineGrainedChecker(const AstStorage &storage, const SymbolTable &symbol_table,
memgraph::query::DbAccessor *dba,
memgraph::glue::FineGrainedAuthChecker *auth_checker) {
ExecutionContext context{dba};
context.symbol_table = symbol_table;
context.evaluation_context.properties = NamesToProperties(storage.properties_, dba);
context.evaluation_context.labels = NamesToLabels(storage.labels_, dba);
context.auth_checker = std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(*auth_checker));
return context;
}
#endif
/** Helper function that collects all the results from the given Produce. */
std::vector<std::vector<TypedValue>> CollectProduce(const Produce &produce, ExecutionContext *context) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,10 @@
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <gtest/gtest.h>
#include "query_plan_common.hpp"
#include <gtest/gtest.h>
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/plan/operator.hpp"
#include "storage/v2/storage.hpp"

View File

@ -11,11 +11,13 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <unordered_map>
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/frontend/semantic/required_privileges.hpp"
#include "storage/v2/id_types.hpp"
#include "utils/license.hpp"
#include "query_common.hpp"
@ -96,12 +98,17 @@ TEST_F(TestPrivilegeExtractor, CreateIndex) {
auto *query = CREATE_INDEX_ON(storage.GetLabelIx(LABEL_0), storage.GetPropertyIx(PROP_0));
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::INDEX));
}
#ifdef MG_ENTERPRISE
TEST_F(TestPrivilegeExtractor, AuthQuery) {
auto *query =
AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{});
memgraph::utils::license::global_license_checker.EnableTesting();
auto label_privileges = std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>{};
auto edge_type_privileges =
std::vector<std::unordered_map<AuthQuery::FineGrainedPrivilege, std::vector<std::string>>>{};
auto *query = AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{},
label_privileges, edge_type_privileges);
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::AUTH));
}
#endif
TEST_F(TestPrivilegeExtractor, ShowIndexInfo) {
auto *query = storage.Create<InfoQuery>();

View File

@ -14,6 +14,7 @@
#include <filesystem>
#include <fmt/format.h>
#include "glue/auth_checker.hpp"
#include "query/auth_checker.hpp"
#include "query/config.hpp"
#include "query/db_accessor.hpp"
@ -21,6 +22,7 @@
#include "query/interpreter.hpp"
#include "query/trigger.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/id_types.hpp"
#include "utils/exceptions.hpp"
#include "utils/memory.hpp"
@ -37,6 +39,12 @@ class MockAuthChecker : public memgraph::query::AuthChecker {
public:
MOCK_CONST_METHOD2(IsUserAuthorized, bool(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges));
#ifdef MG_ENTERPRISE
MOCK_CONST_METHOD2(GetFineGrainedAuthChecker,
std::unique_ptr<memgraph::query::FineGrainedAuthChecker>(
const std::string &username, const memgraph::query::DbAccessor *db_accessor));
#endif
};
} // namespace