[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:
Boris Taševski 2022-09-08 17:48:34 +02:00 committed by GitHub
parent 35f8978560
commit c09b175c76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1271 additions and 259 deletions

View File

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

View File

@ -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`."""
@ -1969,6 +1978,8 @@ def _wrap_exceptions():
raise ValueConversionError(e)
except _mgp.SerializationError as e:
raise SerializationError(e)
except _mgp.AuthorizationError as e:
raise AuthorizationError(e)
return wrapped_func

View File

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

View File

@ -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

View File

@ -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_;
};

View File

@ -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 {

View File

@ -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;
}

View File

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

View File

@ -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)

View File

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

View File

@ -0,0 +1,299 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import pytest
import sys
from common import (
connect,
execute_and_fetch_all,
mgclient,
reset_create_delete_permissions,
)
AUTHORIZATION_ERROR_IDENTIFIER = "AuthorizationError"
create_vertex_query = "CALL create_delete.create_vertex() YIELD created_node RETURN labels(created_node);"
remove_label_vertex_query = "CALL create_delete.remove_label('create_delete_label') YIELD node RETURN labels(node);"
set_label_vertex_query = "CALL create_delete.set_label('new_create_delete_label') YIELD node RETURN labels(node);"
create_edge_query = "MATCH (n:create_delete_label_1), (m:create_delete_label_2) CALL create_delete.create_edge(n, m) YIELD nr_of_edges RETURN nr_of_edges;"
delete_edge_query = "CALL create_delete.delete_edge() YIELD * RETURN *;"
def test_can_not_create_vertex_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_create_vertex_when_given_global_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, create_vertex_query)
len(result[0][0]) == 1
def test_can_not_create_vertex_when_given_global_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_not_create_vertex_when_given_global_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_vertex_query)
def test_can_add_vertex_label_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON LABELS :new_create_delete_label, UPDATE ON LABELS :create_delete_label TO user;",
)
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_label_vertex_query)
assert "create_delete_label" in result[0][0]
assert "new_create_delete_label" in result[0][0]
def test_can_not_add_vertex_label_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor, "GRANT UPDATE ON LABELS :new_create_delete_label, :create_delete_label TO user;"
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, set_label_vertex_query)
def test_can_not_add_vertex_label_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor, "GRANT READ ON LABELS :new_create_delete_label, UPDATE ON LABELS :create_delete_label TO user;"
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, set_label_vertex_query)
def test_can_remove_vertex_label_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, remove_label_vertex_query)
assert result[0][0] != ":create_delete_label"
def test_can_remove_vertex_label_when_given_global_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, remove_label_vertex_query)
assert result[0][0] != ":create_delete_label"
def test_can_not_remove_vertex_label_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_global_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :create_delete_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_remove_vertex_label_when_given_global_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, remove_label_vertex_query)
def test_can_not_create_edge_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_not_create_edge_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON EDGE_TYPES :new_create_delete_edge_type TO user")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_not_create_edge_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON EDGE_TYPES :new_create_delete_edge_type TO user")
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, create_edge_query)
def test_can_create_edge_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON EDGE_TYPES :new_create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
no_of_edges = execute_and_fetch_all(test_cursor, create_edge_query)
assert no_of_edges[0][0] == 2
def test_can_not_delete_edge_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_not_delete_edge_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT READ ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_not_delete_edge_when_given_update():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT UPDATE ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
with pytest.raises(mgclient.DatabaseError, match=AUTHORIZATION_ERROR_IDENTIFIER):
execute_and_fetch_all(test_cursor, delete_edge_query)
def test_can_delete_edge_when_given_create_delete():
admin_cursor = connect(username="admin", password="test").cursor()
reset_create_delete_permissions(admin_cursor)
execute_and_fetch_all(
admin_cursor,
"GRANT CREATE_DELETE ON EDGE_TYPES :create_delete_edge_type TO user",
)
test_cursor = connect(username="user", password="test").cursor()
no_of_edges = execute_and_fetch_all(test_cursor, delete_edge_query)
assert no_of_edges[0][0] == 0
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-rA"]))

View File

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

View File

@ -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)

View File

@ -0,0 +1,63 @@
# Copyright 2021 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import mgp
@mgp.write_proc
def create_vertex(ctx: mgp.ProcCtx) -> mgp.Record(created_node=mgp.Vertex):
vertex = ctx.graph.create_vertex()
return mgp.Record(created_node=vertex)
@mgp.write_proc
def remove_label(ctx: mgp.ProcCtx, label: str) -> mgp.Record(node=mgp.Vertex):
for vertex in ctx.graph.vertices:
if "create_delete_label" in vertex.labels:
break
vertex.remove_label(label)
return mgp.Record(node=vertex)
@mgp.write_proc
def set_label(ctx: mgp.ProcCtx, new_label: str) -> mgp.Record(node=mgp.Vertex):
for vertex in ctx.graph.vertices:
if "create_delete_label" in vertex.labels:
break
vertex.add_label(new_label)
return mgp.Record(node=vertex)
@mgp.write_proc
def create_edge(ctx: mgp.ProcCtx, v1: mgp.Vertex, v2: mgp.Vertex) -> mgp.Record(nr_of_edges=int):
ctx.graph.create_edge(v1, v2, mgp.EdgeType("new_create_delete_edge_type"))
count = 0
for vertex in ctx.graph.vertices:
for _ in vertex.out_edges:
count += 1
return mgp.Record(nr_of_edges=count)
@mgp.write_proc
def delete_edge(ctx: mgp.ProcCtx) -> mgp.Record(edge_count=int):
count = 0
for vertex in ctx.graph.vertices:
for edge in vertex.out_edges:
if edge.type.name == "create_delete_edge_type":
ctx.graph.delete_edge(edge)
else:
count += 1
return mgp.Record(edge_count=count)

View File

@ -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)

View File

@ -0,0 +1,21 @@
# Copyright 2021 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import mgp
@mgp.write_proc
def set_property(ctx: mgp.ProcCtx, object: mgp.Any) -> mgp.Record():
try:
object.properties.set("prop", 2)
except mgp.AuthorizationError:
pass
return mgp.Record()

View File

@ -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__":

View File

@ -0,0 +1,225 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import sys
import pytest
from common import connect, execute_and_fetch_all, reset_permissions
get_number_of_vertices_query = "CALL read.number_of_visible_nodes() YIELD nr_of_nodes RETURN nr_of_nodes;"
get_number_of_edges_query = "CALL read.number_of_visible_edges() YIELD nr_of_edges RETURN nr_of_edges;"
def test_can_read_vertex_through_c_api_when_given_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 1
def test_can_read_vertex_through_c_api_when_given_update_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 1
def test_can_read_vertex_through_c_api_when_given_create_delete_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :read_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 1
def test_can_not_read_vertex_through_c_api_when_given_nothing():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, get_number_of_vertices_query)
assert result[0][0] == 0
def test_can_not_read_vertex_through_c_api_when_given_deny_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "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"]))

View File

@ -0,0 +1,184 @@
# Copyright 2022 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import pytest
import sys
from common import (
connect,
execute_and_fetch_all,
reset_update_permissions,
)
set_vertex_property_query = "MATCH (n:update_label) CALL update.set_property(n) YIELD * RETURN n.prop;"
set_edge_property_query = "MATCH (n:update_label_1)-[r:update_edge_type]->(m:update_label_2) CALL update.set_property(r) YIELD * RETURN r.prop;"
def test_can_not_update_vertex_when_given_read():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT READ ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 1
def test_can_update_vertex_when_given_update_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_update_vertex_when_given_create_delete_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS :update_label TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_update_vertex_when_given_update_global_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT UPDATE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_update_vertex_when_given_create_delete_global_grant_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "GRANT CREATE_DELETE ON LABELS * TO user;")
test_cursor = connect(username="user", password="test").cursor()
result = execute_and_fetch_all(test_cursor, set_vertex_property_query)
assert result[0][0] == 2
def test_can_not_update_vertex_when_denied_update_and_granted_global_update_on_label():
admin_cursor = connect(username="admin", password="test").cursor()
reset_update_permissions(admin_cursor)
execute_and_fetch_all(admin_cursor, "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"]))

View File

@ -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"