[E129-MG < T1006-MG] Expand C API with LBA checks (#527)
* [T1006-MG < T1017-MG] Add LBA checks to all read procedures in C API (#515) * Initial Impl * NextPermittedEdge introduced * revert moving constructor to cpp * edge from and edge to methods expanded with lba check * minor fix * added check to path expand procedure * Added integration tests for read query procedures * additional check * changed iterator type to reference * comments from pr Co-authored-by: Josip Mrden <josip.mrden@memgraph.io> * [T1006-MG < T1018-MG] Add LBA checks to all update procedures in C API (#516) * Initial Impl * NextPermittedEdge introduced * revert moving constructor to cpp * edge from and edge to methods expanded with lba check * minor fix * extended update methods * added check to path expand procedure * Added integration tests for read query procedures * Added integration tests for update query modules * additional check * changed iterator type to reference * fixed bug in Update property for node; fixed 2 e2e tests * replaced enum Co-authored-by: Josip Mrden <josip.mrden@memgraph.io> * [T1006-MG < T1019-MG] Add LBA checks to all Create and Delete procedures in C API (#517) * Initial Impl * NextPermittedEdge introduced * revert moving constructor to cpp * edge from and edge to methods expanded with lba check * minor fix * extended update methods * initial implementation * added check to path expand procedure * Added integration tests for read query procedures * Added integration tests for update query modules * Added unit tests for creation of vertex, adding and removing vertex label * additional check * changed iterator type to reference * Added unit tests for create edge * Corrected query module in create edge * fixed bug in Update property for node; fixed 2 e2e tests * fixed merge errors * Expanded FineGrainedAuthChecker with HasGlobalPermissionOnVertices and HasGlobalPermissionOnEdges * Removed two wrong checks; Added two global checks * return null added * introduced new mgp_error value * fixed endless loop * replaced enum * intermediate * tests updated * PermissionDeniedError -> AuthorizationError rename * rename in enum permission_denied error -> authorization error * mgp_vertex_remove_label check improved * quotes changed; order of imports fixed * string constant introduced * import fixed * yaml format Co-authored-by: Josip Mrden <josip.mrden@memgraph.io> Co-authored-by: Josip Mrden <josip.mrden@memgraph.io>
This commit is contained in:
parent
35f8978560
commit
c09b175c76
@ -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,
|
||||
};
|
||||
///@}
|
||||
|
||||
|
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
|
||||
|
||||
|
@ -28,6 +28,18 @@ bool IsUserAuthorizedLabels(const memgraph::auth::User &user, const memgraph::qu
|
||||
});
|
||||
}
|
||||
|
||||
bool IsUserAuthorizedGloballyLabels(const memgraph::auth::User &user,
|
||||
const memgraph::auth::FineGrainedPermission fine_grained_permission) {
|
||||
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) {
|
||||
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_permission) {
|
||||
@ -125,4 +137,13 @@ bool FineGrainedAuthChecker::Accept(
|
||||
return IsUserAuthorizedEdgeType(user_, dba, edge_type, fine_grained_permission);
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::HasGlobalPermissionOnVertices(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
return IsUserAuthorizedGloballyLabels(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
|
||||
}
|
||||
|
||||
bool FineGrainedAuthChecker::HasGlobalPermissionOnEdges(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const {
|
||||
return IsUserAuthorizedGloballyEdges(user_, FineGrainedPrivilegeToFineGrainedPermission(fine_grained_privilege));
|
||||
};
|
||||
} // namespace memgraph::glue
|
||||
|
@ -12,7 +12,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "auth/auth.hpp"
|
||||
#include "auth/models.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
#include "query/auth_checker.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
@ -54,6 +53,12 @@ class FineGrainedAuthChecker : public query::FineGrainedAuthChecker {
|
||||
bool Accept(const memgraph::query::DbAccessor &dba, const memgraph::storage::EdgeTypeId &edge_type,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_permission) const override;
|
||||
|
||||
bool HasGlobalPermissionOnVertices(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
bool HasGlobalPermissionOnEdges(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const override;
|
||||
|
||||
private:
|
||||
auth::User user_;
|
||||
};
|
||||
|
@ -48,6 +48,12 @@ class FineGrainedAuthChecker {
|
||||
[[nodiscard]] virtual bool Accept(const memgraph::query::DbAccessor &dba,
|
||||
const memgraph::storage::EdgeTypeId &edge_type,
|
||||
query::AuthQuery::FineGrainedPrivilege fine_grained_permission) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool HasGlobalPermissionOnVertices(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
|
||||
[[nodiscard]] virtual bool HasGlobalPermissionOnEdges(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege fine_grained_privilege) const = 0;
|
||||
};
|
||||
|
||||
class AllowEverythingFineGrainedAuthChecker final : public query::FineGrainedAuthChecker {
|
||||
@ -71,6 +77,16 @@ class AllowEverythingFineGrainedAuthChecker final : public query::FineGrainedAut
|
||||
const query::AuthQuery::FineGrainedPrivilege fine_grained_permission) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasGlobalPermissionOnVertices(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasGlobalPermissionOnEdges(
|
||||
const memgraph::query::AuthQuery::FineGrainedPrivilege /*fine_grained_privilege*/) const override {
|
||||
return true;
|
||||
}
|
||||
}; // namespace memgraph::query
|
||||
|
||||
class AllowEverythingAuthChecker final : public query::AuthChecker {
|
||||
|
@ -120,6 +120,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>;
|
||||
|
||||
@ -160,6 +164,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;
|
||||
@ -1066,6 +1073,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) {
|
||||
@ -1579,9 +1587,16 @@ 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([=] {
|
||||
if (v->graph->ctx && v->graph->ctx->auth_checker &&
|
||||
!v->graph->ctx->auth_checker->Accept(*v->graph->ctx->db_accessor, v->impl, v->graph->view,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for setting a property on vertex!"};
|
||||
}
|
||||
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot set a property on an immutable vertex!"};
|
||||
}
|
||||
|
||||
const auto prop_key = v->graph->impl->NameToProperty(property_name);
|
||||
const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value));
|
||||
if (result.HasError()) {
|
||||
@ -1619,6 +1634,14 @@ 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([=] {
|
||||
if (v->graph->ctx && v->graph->ctx->auth_checker &&
|
||||
!(v->graph->ctx->auth_checker->Accept(*v->graph->ctx->db_accessor, v->impl, v->graph->view,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
|
||||
v->graph->ctx->auth_checker->Accept(*v->graph->ctx->db_accessor, {v->graph->impl->NameToLabel(label.name)},
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) {
|
||||
throw AuthorizationException{"Insufficient permissions for adding a label to vertex!"};
|
||||
}
|
||||
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot add a label to an immutable vertex!"};
|
||||
}
|
||||
@ -1651,6 +1674,14 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) {
|
||||
|
||||
mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) {
|
||||
return WrapExceptions([=] {
|
||||
if (v->graph->ctx && v->graph->ctx->auth_checker &&
|
||||
!(v->graph->ctx->auth_checker->Accept(*v->graph->ctx->db_accessor, v->impl, v->graph->view,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE) &&
|
||||
v->graph->ctx->auth_checker->Accept(*v->graph->ctx->db_accessor, {v->graph->impl->NameToLabel(label.name)},
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE))) {
|
||||
throw AuthorizationException{"Insufficient permissions for removing a label from vertex!"};
|
||||
}
|
||||
|
||||
if (!MgpVertexIsMutable(*v)) {
|
||||
throw ImmutableObjectException{"Cannot remove a label from an immutable vertex!"};
|
||||
}
|
||||
@ -1828,6 +1859,32 @@ 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); }
|
||||
|
||||
namespace {
|
||||
void NextPermittedEdge(mgp_edges_iterator &it, const bool for_in) {
|
||||
if (!it.source_vertex.graph->ctx || !it.source_vertex.graph->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 db_accessor = *it.source_vertex.graph->ctx->db_accessor;
|
||||
const auto view = it.source_vertex.graph->view;
|
||||
while (*impl_it != end) {
|
||||
if (auth_checker->Accept(db_accessor, **impl_it, memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
|
||||
const auto &check_vertex = it.source_vertex.impl == (*impl_it)->From() ? (*impl_it)->To() : (*impl_it)->From();
|
||||
if (auth_checker->Accept(db_accessor, check_vertex, view,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::READ)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++*impl_it;
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
mgp_error mgp_vertex_iter_in_edges(mgp_vertex *v, mgp_memory *memory, mgp_edges_iterator **result) {
|
||||
return WrapExceptions(
|
||||
[v, memory] {
|
||||
@ -1851,6 +1908,9 @@ 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());
|
||||
|
||||
NextPermittedEdge(*it, true);
|
||||
|
||||
if (*it->in_it != it->in->end()) {
|
||||
it->current_e.emplace(**it->in_it, v->graph, it->GetMemoryResource());
|
||||
}
|
||||
@ -1883,6 +1943,9 @@ 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());
|
||||
|
||||
NextPermittedEdge(*it, false);
|
||||
|
||||
if (*it->out_it != it->out->end()) {
|
||||
it->current_e.emplace(**it->out_it, v->graph, it->GetMemoryResource());
|
||||
}
|
||||
@ -1911,24 +1974,35 @@ 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;
|
||||
|
||||
NextPermittedEdge(*it, for_in);
|
||||
|
||||
if (*impl_it == end) {
|
||||
it->current_e = std::nullopt;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
it->current_e.emplace(**impl_it, it->source_vertex.graph, it->GetMemoryResource());
|
||||
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);
|
||||
}
|
||||
@ -2002,6 +2076,12 @@ 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([=] {
|
||||
if (e->from.graph->ctx && e->from.graph->ctx->auth_checker &&
|
||||
!e->from.graph->ctx->auth_checker->Accept(*e->from.graph->ctx->db_accessor, e->impl,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for setting a property on edge!"};
|
||||
}
|
||||
|
||||
if (!MgpEdgeIsMutable(*e)) {
|
||||
throw ImmutableObjectException{"Cannot set a property on an immutable edge!"};
|
||||
}
|
||||
@ -2057,7 +2137,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:
|
||||
@ -2088,7 +2169,13 @@ 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 * {
|
||||
if (graph->ctx && graph->ctx->auth_checker &&
|
||||
!graph->ctx->auth_checker->HasGlobalPermissionOnVertices(
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for creating vertices!"};
|
||||
}
|
||||
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"};
|
||||
}
|
||||
@ -2107,6 +2194,12 @@ 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([=] {
|
||||
if (graph->ctx && graph->ctx->auth_checker &&
|
||||
!graph->ctx->auth_checker->Accept(*graph->ctx->db_accessor, vertex->impl, graph->view,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for deleting a vertex!"};
|
||||
}
|
||||
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
|
||||
}
|
||||
@ -2142,6 +2235,12 @@ 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([=] {
|
||||
if (graph->ctx && graph->ctx->auth_checker &&
|
||||
!graph->ctx->auth_checker->Accept(*graph->ctx->db_accessor, vertex->impl, graph->view,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for deleting a vertex!"};
|
||||
}
|
||||
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot remove a vertex from an immutable graph!"};
|
||||
}
|
||||
@ -2188,7 +2287,13 @@ 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 * {
|
||||
if (graph->ctx && graph->ctx->auth_checker &&
|
||||
!graph->ctx->auth_checker->Accept(*graph->ctx->db_accessor, from->graph->impl->NameToEdgeType(type.name),
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for creating edges!"};
|
||||
}
|
||||
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot create an edge in an immutable graph!"};
|
||||
}
|
||||
@ -2221,6 +2326,11 @@ 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([=] {
|
||||
if (graph->ctx && graph->ctx->auth_checker &&
|
||||
!graph->ctx->auth_checker->Accept(*graph->ctx->db_accessor, edge->impl,
|
||||
memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) {
|
||||
throw AuthorizationException{"Insufficient permissions for deleting an edge!"};
|
||||
}
|
||||
if (!MgpGraphIsMutable(*graph)) {
|
||||
throw ImmutableObjectException{"Cannot remove an edge from an immutable graph!"};
|
||||
}
|
||||
@ -2253,7 +2363,7 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
|
||||
|
||||
namespace {
|
||||
void NextPermitted(mgp_vertices_iterator &it) {
|
||||
if (!it.graph->ctx->auth_checker) {
|
||||
if (!it.graph->ctx || !it.graph->ctx->auth_checker) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -3,10 +3,11 @@ function(copy_lba_procedures_e2e_python_files FILE_NAME)
|
||||
endfunction()
|
||||
|
||||
copy_lba_procedures_e2e_python_files(common.py)
|
||||
copy_lba_procedures_e2e_python_files(lba_procedures.py)
|
||||
copy_lba_procedures_e2e_python_files(show_privileges.py)
|
||||
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)
|
||||
|
@ -24,7 +24,7 @@ def connect(**kwargs) -> mgclient.Connection:
|
||||
return connection
|
||||
|
||||
|
||||
def reset_permissions(admin_cursor: mgclient.Cursor, create_index: bool):
|
||||
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;")
|
||||
@ -32,6 +32,9 @@ def reset_permissions(admin_cursor: mgclient.Cursor, create_index: bool):
|
||||
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;")
|
||||
@ -41,11 +44,26 @@ def reset_permissions(admin_cursor: mgclient.Cursor, create_index: bool):
|
||||
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, "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]->(m:update_label_2);",
|
||||
"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"]))
|
@ -1,30 +0,0 @@
|
||||
# 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
|
||||
|
||||
|
||||
def test_lba_procedures_vertices_iterator_count_only_permitted_vertices():
|
||||
cursor = connect(username="Josip", password="").cursor()
|
||||
result = execute_and_fetch_all(cursor, "CALL read.number_of_visible_nodes() YIELD nr_of_nodes RETURN nr_of_nodes ;")
|
||||
|
||||
assert result[0][0] == 10
|
||||
|
||||
cursor = connect(username="Boris", password="").cursor()
|
||||
result = execute_and_fetch_all(cursor, "CALL read.number_of_visible_nodes() YIELD nr_of_nodes RETURN nr_of_nodes ;")
|
||||
|
||||
assert result[0][0] == 6
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-rA"]))
|
@ -1 +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)
|
@ -15,3 +15,13 @@ 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()
|
@ -19,10 +19,10 @@ 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:read_label) RETURN n;"
|
||||
match_by_label_property_range_query = "MATCH (n:read_label) WHERE n.prop < 7 RETURN n;"
|
||||
match_by_label_property_value_query = "MATCH (n:read_label {prop: 5}) RETURN n;"
|
||||
match_by_label_property_query = "MATCH (n:read_label) WHERE n.prop IS NOT NULL 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 = [
|
||||
@ -34,6 +34,7 @@ read_node_without_index_operation_cases = [
|
||||
["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;"],
|
||||
@ -44,6 +45,7 @@ read_node_with_index_operation_cases = [
|
||||
["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 = [
|
||||
[],
|
||||
@ -67,6 +69,7 @@ not_read_node_without_index_operation_cases = [
|
||||
],
|
||||
]
|
||||
|
||||
not_read_node_without_index_operation_cases_expected_sizes = [0, 0, 0, 0, 2, 0, 2]
|
||||
|
||||
not_read_node_with_index_operation_cases = [
|
||||
[],
|
||||
@ -90,6 +93,8 @@ not_read_node_with_index_operation_cases = [
|
||||
],
|
||||
]
|
||||
|
||||
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()
|
||||
@ -100,7 +105,7 @@ def get_user_cursor():
|
||||
|
||||
|
||||
def execute_read_node_assertion(
|
||||
operation_case: List[str], queries: List[str], create_index: bool, can_read: bool
|
||||
operation_case: List[str], queries: List[str], create_index: bool, expected_size: int
|
||||
) -> None:
|
||||
admin_cursor = get_admin_cursor()
|
||||
user_cursor = get_user_cursor()
|
||||
@ -110,10 +115,9 @@ def execute_read_node_assertion(
|
||||
for operation in operation_case:
|
||||
execute_and_fetch_all(admin_cursor, operation)
|
||||
|
||||
read_size = 1 if can_read else 0
|
||||
for mq in queries:
|
||||
results = execute_and_fetch_all(user_cursor, mq)
|
||||
assert len(results) == read_size
|
||||
assert len(results) == expected_size
|
||||
|
||||
|
||||
def test_can_read_node_when_authorized():
|
||||
@ -125,10 +129,14 @@ def test_can_read_node_when_authorized():
|
||||
match_by_label_property_value_query,
|
||||
]
|
||||
|
||||
for operation_case in read_node_without_index_operation_cases:
|
||||
execute_read_node_assertion(operation_case, match_queries_without_index, False, True)
|
||||
for operation_case in read_node_with_index_operation_cases:
|
||||
execute_read_node_assertion(operation_case, match_queries_with_index, True, True)
|
||||
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():
|
||||
@ -140,10 +148,14 @@ def test_can_not_read_node_when_authorized():
|
||||
match_by_label_property_value_query,
|
||||
]
|
||||
|
||||
for operation_case in not_read_node_without_index_operation_cases:
|
||||
execute_read_node_assertion(operation_case, match_queries_without_index, False, False)
|
||||
for operation_case in not_read_node_with_index_operation_cases:
|
||||
execute_read_node_assertion(operation_case, match_queries_with_index, True, False)
|
||||
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__":
|
||||
|
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, "DENY 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] == 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, "DENY READ 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, "DENY READ 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, "DENY READ 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, "DENY 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] == 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, "DENY READ 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, "DENY READ 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, "DENY READ 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"]))
|
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, "DENY UPDATE 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, "DENY UPDATE 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, "DENY UPDATE ON EDGE_TYPES :update_edge_type TO user;")
|
||||
execute_and_fetch_all(admin_cursor, "DENY 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
|
||||
|
||||
|
||||
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, "DENY UPDATE ON EDGE_TYPES :update_edge_type TO user;")
|
||||
execute_and_fetch_all(admin_cursor, "DENY CREATE_DELETE 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"]))
|
@ -1,22 +1,25 @@
|
||||
template_cluster: &template_cluster
|
||||
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 (:Label1 {id: 1}) ;"
|
||||
- "Create (:Label1 {id: 2}) ;"
|
||||
- "Create (:Label1 {id: 3}) ;"
|
||||
- "Create (:Label1 {id: 4}) ;"
|
||||
- "Create (:Label1 {id: 5}) ;"
|
||||
- "Create (:Label1 {id: 6}) ;"
|
||||
- "Create (:Label2 {id: 1}) ;"
|
||||
- "Create (:Label2 {id: 2}) ;"
|
||||
- "Create (:Label2 {id: 3}) ;"
|
||||
- "Create (:Label2 {id: 4}) ;"
|
||||
- "Create User Josip ;"
|
||||
- "Create User Boris ;"
|
||||
- "Grant Read On Labels :Label1 to Boris;"
|
||||
- "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
|
||||
@ -54,8 +57,21 @@ show_privileges_cluster: &show_privileges_cluster
|
||||
- "Create User Bruno;"
|
||||
- "Grant Auth to Bruno;"
|
||||
- "Deny Create_Delete 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"]
|
||||
@ -77,15 +93,26 @@ update_permission_queries_cluster: &update_permission_queries_cluster
|
||||
- "GRANT ALL PRIVILEGES TO admin;"
|
||||
- "CREATE USER user IDENTIFIED BY 'test'"
|
||||
- "GRANT ALL PRIVILEGES TO user;"
|
||||
|
||||
validation_queries: []
|
||||
|
||||
workloads:
|
||||
- name: "Label-based auth"
|
||||
- name: "read-query-modules"
|
||||
binary: "tests/e2e/pytest_runner.sh"
|
||||
proc: "tests/e2e/lba_procedures/procedures/"
|
||||
args: ["lba_procedures/lba_procedures.py"]
|
||||
<<: *template_cluster
|
||||
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"
|
||||
|
Loading…
Reference in New Issue
Block a user