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:
commit
898c894a48
2
.github/workflows/diff.yaml
vendored
2
.github/workflows/diff.yaml
vendored
@ -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
|
||||
|
@ -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,
|
||||
};
|
||||
///@}
|
||||
|
||||
|
@ -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 ¶meter : parameters) {
|
||||
auto parameter_name = parameter.name.data();
|
||||
|
||||
if (!parameter.optional) {
|
||||
|
361
include/mgp.py
361
include/mgp.py
@ -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. Doesn’t 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. Doesn’t 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. Doesn’t 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
|
||||
|
||||
|
@ -173,6 +173,10 @@ class SerializationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def type_nullable(elem: Any):
|
||||
pass
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
4
src/glue/CMakeLists.txt
Normal 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)
|
@ -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
|
||||
|
@ -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
171
src/glue/auth_checker.cpp
Normal 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
67
src/glue/auth_checker.hpp
Normal 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
664
src/glue/auth_handler.cpp
Normal 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
109
src/glue/auth_handler.hpp
Normal 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
|
394
src/memgraph.cpp
394
src/memgraph.cpp
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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!");
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 ;
|
||||
|
||||
|
@ -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 ;
|
||||
|
@ -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(
|
||||
|
@ -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 ¶meters,
|
||||
InterpreterContext *interpreter_context, DbAccessor *db_accessor,
|
||||
@ -932,7 +975,7 @@ struct PullPlanVector {
|
||||
struct PullPlan {
|
||||
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters ¶meters, 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 ¶meters, 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,
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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(),
|
||||
[¤t_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 ¤t_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
|
||||
|
@ -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,
|
||||
|
@ -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; }
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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})
|
||||
|
8
tests/e2e/fine_grained_access/CMakeLists.txt
Normal file
8
tests/e2e/fine_grained_access/CMakeLists.txt
Normal 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)
|
31
tests/e2e/fine_grained_access/common.py
Normal file
31
tests/e2e/fine_grained_access/common.py
Normal 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
|
517
tests/e2e/fine_grained_access/create_delete_filtering_tests.py
Normal file
517
tests/e2e/fine_grained_access/create_delete_filtering_tests.py
Normal 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"]))
|
94
tests/e2e/fine_grained_access/edge_type_filtering_tests.py
Normal file
94
tests/e2e/fine_grained_access/edge_type_filtering_tests.py
Normal 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"]))
|
717
tests/e2e/fine_grained_access/path_filtering_tests.py
Normal file
717
tests/e2e/fine_grained_access/path_filtering_tests.py
Normal 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"]))
|
74
tests/e2e/fine_grained_access/workloads.yaml
Normal file
74
tests/e2e/fine_grained_access/workloads.yaml
Normal 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
|
13
tests/e2e/lba_procedures/CMakeLists.txt
Normal file
13
tests/e2e/lba_procedures/CMakeLists.txt
Normal 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)
|
69
tests/e2e/lba_procedures/common.py
Normal file
69
tests/e2e/lba_procedures/common.py
Normal 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);",
|
||||
)
|
299
tests/e2e/lba_procedures/create_delete_query_modules.py
Normal file
299
tests/e2e/lba_procedures/create_delete_query_modules.py
Normal 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"]))
|
3
tests/e2e/lba_procedures/procedures/CMakeLists.txt
Normal file
3
tests/e2e/lba_procedures/procedures/CMakeLists.txt
Normal 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)
|
63
tests/e2e/lba_procedures/procedures/create_delete.py
Normal file
63
tests/e2e/lba_procedures/procedures/create_delete.py
Normal 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)
|
27
tests/e2e/lba_procedures/procedures/read.py
Normal file
27
tests/e2e/lba_procedures/procedures/read.py
Normal 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)
|
21
tests/e2e/lba_procedures/procedures/update.py
Normal file
21
tests/e2e/lba_procedures/procedures/update.py
Normal 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()
|
162
tests/e2e/lba_procedures/read_permission_queries.py
Normal file
162
tests/e2e/lba_procedures/read_permission_queries.py
Normal 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"]))
|
225
tests/e2e/lba_procedures/read_query_modules.py
Normal file
225
tests/e2e/lba_procedures/read_query_modules.py
Normal 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"]))
|
117
tests/e2e/lba_procedures/show_privileges.py
Normal file
117
tests/e2e/lba_procedures/show_privileges.py
Normal 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"]))
|
149
tests/e2e/lba_procedures/update_permission_queries.py
Normal file
149
tests/e2e/lba_procedures/update_permission_queries.py
Normal 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"]))
|
184
tests/e2e/lba_procedures/update_query_modules.py
Normal file
184
tests/e2e/lba_procedures/update_query_modules.py
Normal 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"]))
|
133
tests/e2e/lba_procedures/workloads.yaml
Normal file
133
tests/e2e/lba_procedures/workloads.yaml
Normal 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
|
@ -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)
|
||||
#
|
||||
|
11
tests/integration/fine_grained_access/CMakeLists.txt
Normal file
11
tests/integration/fine_grained_access/CMakeLists.txt
Normal 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)
|
59
tests/integration/fine_grained_access/filtering.cpp
Normal file
59
tests/integration/fine_grained_access/filtering.cpp
Normal 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;
|
||||
}
|
135
tests/integration/fine_grained_access/runner.py
Normal file
135
tests/integration/fine_grained_access/runner.py
Normal 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)
|
84
tests/integration/fine_grained_access/tester.cpp
Normal file
84
tests/integration/fine_grained_access/tester.cpp
Normal 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;
|
||||
}
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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()})
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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
211
tests/unit/auth_checker.cpp
Normal 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
733
tests/unit/auth_handler.cpp
Normal 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
|
@ -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
|
||||
|
115
tests/unit/bfs_fine_grained.cpp
Normal file
115
tests/unit/bfs_fine_grained.cpp
Normal 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
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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__)
|
||||
|
@ -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
@ -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"
|
||||
|
@ -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>();
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user