[E129-MG <-T0982-MG] implement edge type filtering ()

* GRANT, REVOKE, DENY and access_checker DONE

* Added AccessChecker to ExecutionContext

* grammar expanded; ()

* current

* T0954 mg expand user and role to hold permissions on labels ()

* added FineGrainedAccessPermissions class to model

* expanded user and role with fine grained access permissions

* fixed grammar

* [E129 < T0953-MG] GRANT, DENY, REVOKE added in interpreter and mainVisitor ()

* GRANT, DENY, REVOKE added in interpreter and mainVisitor

* Commented labelPermissons

* remove labelsPermission adding

* Fixed

* Removed extra lambda

* fixed

* [E129<-T0955-MG] Expand ExecutionContext with label related information ()

* added

* Added FineGrainedAccessChecker to Context

* fixed

* Added filtering

* testing

* Added edge filtering to storage, need to add filtering in simple Expand in operator.cpp

* Removed storage changes

* MATCH filtering working

* EdgeTypeFiltering working, just need to test everything again

* Removed FineGrainedAccessChecker

* Removed Expand Path

* Fix

* Tested FineGrainedAccessHandler, need to test AuthChecker

* Added integration test for lba

* Fixed merge conflicts

* PR fix

* fixed

* PR fix

* Fix test

* removed .vscode, .cache, .githooks

* githooks

* added tests

* fixed build

* Changed ast.lcp and User pointer to value in context.hpp

* Fixed test

* Remove denies on grant all

* AuthChecker

* Pr fix, auth_checker still not fixed

* Create mg-glue and extract UserBasedAuthChecker from AuthChecker

* Build fixed, need to fix test

* e2e tests

* e2e test working

* Added unit test, e2e and FineGrainedChecker

* Mege E129, auth_checker tests

* Fixed test

* e2e fix

Co-authored-by: Boris Taševski <36607228+BorisTasevski@users.noreply.github.com>
Co-authored-by: josipmrden <josip.mrden@external-basf.com>
Co-authored-by: János Benjamin Antal <benjamin.antal@memgraph.io>
This commit is contained in:
niko4299 2022-08-16 15:57:23 +02:00 committed by GitHub
parent a98463b0bd
commit e15576f56c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1266 additions and 235 deletions

View File

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

View File

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

View File

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

View File

@ -8,7 +8,10 @@
#include "auth/models.hpp"
#include <algorithm>
#include <iterator>
#include <regex>
#include <unordered_set>
#include <gflags/gflags.h>
@ -171,7 +174,7 @@ Permissions Permissions::Deserialize(const nlohmann::json &data) {
if (!data["grants"].is_number_unsigned() || !data["denies"].is_number_unsigned()) {
throw AuthException("Couldn't load permissions data!");
}
return {data["grants"], data["denies"]};
return Permissions{data["grants"], data["denies"]};
}
uint64_t Permissions::grants() const { return grants_; }
@ -204,6 +207,7 @@ PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission)
void FineGrainedAccessPermissions::Grant(const std::string &permission) {
if (permission == ASTERISK) {
grants_.clear();
denies_.clear();
grants_.insert(permission);
return;
@ -246,6 +250,7 @@ void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
void FineGrainedAccessPermissions::Deny(const std::string &permission) {
if (permission == ASTERISK) {
grants_.clear();
denies_.clear();
denies_.insert(permission);
@ -293,27 +298,66 @@ bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAcce
return !(first == second);
}
FineGrainedAccessHandler::FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions,
FineGrainedAccessPermissions edgeTypePermissions)
: label_permissions_(std::move(labelPermissions)), edge_type_permissions_(std::move(edgeTypePermissions)) {}
const FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() const { return label_permissions_; }
FineGrainedAccessPermissions &FineGrainedAccessHandler::label_permissions() { return label_permissions_; }
const FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() const {
return edge_type_permissions_;
}
FineGrainedAccessPermissions &FineGrainedAccessHandler::edge_type_permissions() { return edge_type_permissions_; }
nlohmann::json FineGrainedAccessHandler::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["label_permissions"] = label_permissions_.Serialize();
data["edge_type_permissions"] = edge_type_permissions_.Serialize();
return data;
}
FineGrainedAccessHandler FineGrainedAccessHandler::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load role data!");
}
if (!data["label_permissions"].is_object() && !data["edge_type_permissions"].is_object()) {
throw AuthException("Couldn't load label_permissions or edge_type_permissions data!");
}
auto label_permissions = FineGrainedAccessPermissions::Deserialize(data["label_permissions"]);
auto edge_type_permissions = FineGrainedAccessPermissions::Deserialize(data["edge_type_permissions"]);
return FineGrainedAccessHandler(std::move(label_permissions), std::move(edge_type_permissions));
}
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
return first.label_permissions_ == second.label_permissions_ &&
first.edge_type_permissions_ == second.edge_type_permissions_;
}
bool operator!=(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second) {
return !(first == second);
}
Role::Role(const std::string &rolename) : rolename_(utils::ToLowerCase(rolename)) {}
Role::Role(const std::string &rolename, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions)
FineGrainedAccessHandler fine_grained_access_handler)
: rolename_(utils::ToLowerCase(rolename)),
permissions_(permissions),
fine_grained_access_permissions_(fine_grained_access_permissions) {}
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
const std::string &Role::rolename() const { return rolename_; }
const Permissions &Role::permissions() const { return permissions_; }
Permissions &Role::permissions() { return permissions_; }
const FineGrainedAccessPermissions &Role::fine_grained_access_permissions() const {
return fine_grained_access_permissions_;
}
FineGrainedAccessPermissions &Role::fine_grained_access_permissions() { return fine_grained_access_permissions_; }
const FineGrainedAccessHandler &Role::fine_grained_access_handler() const { return fine_grained_access_handler_; }
FineGrainedAccessHandler &Role::fine_grained_access_handler() { return fine_grained_access_handler_; }
nlohmann::json Role::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["rolename"] = rolename_;
data["permissions"] = permissions_.Serialize();
data["fine_grained_access_permissions"] = fine_grained_access_permissions_.Serialize();
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
return data;
}
@ -322,27 +366,29 @@ Role Role::Deserialize(const nlohmann::json &data) {
throw AuthException("Couldn't load role data!");
}
if (!data["rolename"].is_string() || !data["permissions"].is_object() ||
!data["fine_grained_access_permissions"].is_object()) {
!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load role data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
auto fine_grained_access_permissions =
FineGrainedAccessPermissions::Deserialize(data["fine_grained_access_permissions"]);
return {data["rolename"], permissions, fine_grained_access_permissions};
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["rolename"], permissions, std::move(fine_grained_access_handler)};
}
bool operator==(const Role &first, const Role &second) {
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_;
return first.rolename_ == second.rolename_ && first.permissions_ == second.permissions_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
User::User() {}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions)
FineGrainedAccessHandler fine_grained_access_handler)
: username_(utils::ToLowerCase(username)),
password_hash_(password_hash),
permissions_(permissions),
fine_grained_access_permissions_(fine_grained_access_permissions) {}
fine_grained_access_handler_(std::move(fine_grained_access_handler)) {}
bool User::CheckPassword(const std::string &password) {
if (password_hash_.empty()) return true;
@ -385,41 +431,64 @@ void User::ClearRole() { role_ = std::nullopt; }
Permissions User::GetPermissions() const {
if (role_) {
return Permissions(permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies());
return Permissions{permissions_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies()};
}
return permissions_;
}
FineGrainedAccessPermissions User::GetFineGrainedAccessPermissions() const {
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
if (role_) {
std::unordered_set<std::string> resultGrants;
std::set_union(fine_grained_access_permissions_.grants().begin(), fine_grained_access_permissions_.grants().end(),
role_->fine_grained_access_permissions().grants().begin(),
role_->fine_grained_access_permissions().grants().end(),
std::set_union(fine_grained_access_handler_.label_permissions().grants().begin(),
fine_grained_access_handler_.label_permissions().grants().end(),
role_->fine_grained_access_handler().label_permissions().grants().begin(),
role_->fine_grained_access_handler().label_permissions().grants().end(),
std::inserter(resultGrants, resultGrants.begin()));
std::unordered_set<std::string> resultDenies;
std::set_union(fine_grained_access_permissions_.denies().begin(), fine_grained_access_permissions_.denies().end(),
role_->fine_grained_access_permissions().denies().begin(),
role_->fine_grained_access_permissions().denies().end(),
std::set_union(fine_grained_access_handler_.label_permissions().denies().begin(),
fine_grained_access_handler_.label_permissions().denies().end(),
role_->fine_grained_access_handler().label_permissions().denies().begin(),
role_->fine_grained_access_handler().label_permissions().denies().end(),
std::inserter(resultDenies, resultDenies.begin()));
return FineGrainedAccessPermissions(resultGrants, resultDenies);
}
return fine_grained_access_permissions_;
return fine_grained_access_handler_.label_permissions();
}
FineGrainedAccessPermissions User::GetFineGrainedAccessEdgeTypePermissions() const {
if (role_) {
std::unordered_set<std::string> resultGrants;
std::set_union(fine_grained_access_handler_.edge_type_permissions().grants().begin(),
fine_grained_access_handler_.edge_type_permissions().grants().end(),
role_->fine_grained_access_handler().edge_type_permissions().grants().begin(),
role_->fine_grained_access_handler().edge_type_permissions().grants().end(),
std::inserter(resultGrants, resultGrants.begin()));
std::unordered_set<std::string> resultDenies;
std::set_union(fine_grained_access_handler_.edge_type_permissions().denies().begin(),
fine_grained_access_handler_.edge_type_permissions().denies().end(),
role_->fine_grained_access_handler().edge_type_permissions().denies().begin(),
role_->fine_grained_access_handler().edge_type_permissions().denies().end(),
std::inserter(resultDenies, resultDenies.begin()));
return FineGrainedAccessPermissions(resultGrants, resultDenies);
}
return fine_grained_access_handler_.edge_type_permissions();
}
const std::string &User::username() const { return username_; }
const Permissions &User::permissions() const { return permissions_; }
Permissions &User::permissions() { return permissions_; }
const FineGrainedAccessPermissions &User::fine_grained_access_permissions() const {
return fine_grained_access_permissions_;
}
FineGrainedAccessPermissions &User::fine_grained_access_permissions() { return fine_grained_access_permissions_; }
const FineGrainedAccessHandler &User::fine_grained_access_handler() const { return fine_grained_access_handler_; }
FineGrainedAccessHandler &User::fine_grained_access_handler() { return fine_grained_access_handler_; }
const Role *User::role() const {
if (role_.has_value()) {
@ -433,7 +502,7 @@ nlohmann::json User::Serialize() const {
data["username"] = username_;
data["password_hash"] = password_hash_;
data["permissions"] = permissions_.Serialize();
data["fine_grained_access_permissions"] = fine_grained_access_permissions_.Serialize();
data["fine_grained_access_handler"] = fine_grained_access_handler_.Serialize();
// The role shouldn't be serialized here, it is stored as a foreign key.
return data;
}
@ -442,17 +511,19 @@ User User::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load user data!");
}
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object()) {
if (!data["username"].is_string() || !data["password_hash"].is_string() || !data["permissions"].is_object() ||
!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load user data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
auto fine_grained_access_permissions =
FineGrainedAccessPermissions::Deserialize(data["fine_grained_access_permissions"]);
return {data["username"], data["password_hash"], permissions, fine_grained_access_permissions};
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler};
}
bool operator==(const User &first, const User &second) {
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_;
first.permissions_ == second.permissions_ && first.role_ == second.role_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
} // namespace memgraph::auth

View File

@ -13,6 +13,7 @@
#include <unordered_set>
#include <json/json.hpp>
#include <unordered_set>
namespace memgraph::auth {
// These permissions must have values that are applicable for usage in a
@ -58,7 +59,13 @@ std::string PermissionLevelToString(PermissionLevel level);
class Permissions final {
public:
Permissions(uint64_t grants = 0, uint64_t denies = 0);
explicit Permissions(uint64_t grants = 0, uint64_t denies = 0);
Permissions(const Permissions &) = default;
Permissions &operator=(const Permissions &) = default;
Permissions(Permissions &&) noexcept = default;
Permissions &operator=(Permissions &&) noexcept = default;
~Permissions() = default;
PermissionLevel Has(Permission permission) const;
@ -94,6 +101,12 @@ class FineGrainedAccessPermissions final {
explicit FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants = {},
const std::unordered_set<std::string> &denies = {});
FineGrainedAccessPermissions(const FineGrainedAccessPermissions &) = default;
FineGrainedAccessPermissions &operator=(const FineGrainedAccessPermissions &) = default;
FineGrainedAccessPermissions(FineGrainedAccessPermissions &&) = default;
FineGrainedAccessPermissions &operator=(FineGrainedAccessPermissions &&) = default;
~FineGrainedAccessPermissions() = default;
PermissionLevel Has(const std::string &permission) const;
void Grant(const std::string &permission);
@ -119,18 +132,55 @@ bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAcce
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
class FineGrainedAccessHandler final {
public:
explicit FineGrainedAccessHandler(FineGrainedAccessPermissions labelPermissions = FineGrainedAccessPermissions(),
FineGrainedAccessPermissions edgeTypePermissions = FineGrainedAccessPermissions());
FineGrainedAccessHandler(const FineGrainedAccessHandler &) = default;
FineGrainedAccessHandler &operator=(const FineGrainedAccessHandler &) = default;
FineGrainedAccessHandler(FineGrainedAccessHandler &&) noexcept = default;
FineGrainedAccessHandler &operator=(FineGrainedAccessHandler &&) noexcept = default;
~FineGrainedAccessHandler() = default;
const FineGrainedAccessPermissions &label_permissions() const;
FineGrainedAccessPermissions &label_permissions();
const FineGrainedAccessPermissions &edge_type_permissions() const;
FineGrainedAccessPermissions &edge_type_permissions();
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static FineGrainedAccessHandler Deserialize(const nlohmann::json &data);
friend bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
private:
FineGrainedAccessPermissions label_permissions_;
FineGrainedAccessPermissions edge_type_permissions_;
};
bool operator==(const FineGrainedAccessHandler &first, const FineGrainedAccessHandler &second);
class Role final {
public:
Role(const std::string &rolename);
explicit Role(const std::string &rolename);
Role(const std::string &rolename, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions);
FineGrainedAccessHandler fine_grained_access_handler);
Role(const Role &) = default;
Role &operator=(const Role &) = default;
Role(Role &&) noexcept = default;
Role &operator=(Role &&) noexcept = default;
~Role() = default;
const std::string &rolename() const;
const Permissions &permissions() const;
Permissions &permissions();
const FineGrainedAccessPermissions &fine_grained_access_permissions() const;
FineGrainedAccessPermissions &fine_grained_access_permissions();
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
nlohmann::json Serialize() const;
@ -142,7 +192,7 @@ class Role final {
private:
std::string rolename_;
Permissions permissions_;
FineGrainedAccessPermissions fine_grained_access_permissions_;
FineGrainedAccessHandler fine_grained_access_handler_;
};
bool operator==(const Role &first, const Role &second);
@ -150,10 +200,18 @@ bool operator==(const Role &first, const Role &second);
// TODO (mferencevic): Implement password expiry.
class User final {
public:
User(const std::string &username);
User();
explicit User(const std::string &username);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
const FineGrainedAccessPermissions &fine_grained_access_permissions);
FineGrainedAccessHandler fine_grained_access_handler);
User(const User &) = default;
User &operator=(const User &) = default;
User(User &&) noexcept = default;
User &operator=(User &&) noexcept = default;
~User() = default;
/// @throw AuthException if unable to verify the password.
bool CheckPassword(const std::string &password);
@ -166,14 +224,15 @@ class User final {
void ClearRole();
Permissions GetPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
const std::string &username() const;
const Permissions &permissions() const;
Permissions &permissions();
const FineGrainedAccessPermissions &fine_grained_access_permissions() const;
FineGrainedAccessPermissions &fine_grained_access_permissions();
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
const Role *role() const;
@ -188,7 +247,7 @@ class User final {
std::string username_;
std::string password_hash_;
Permissions permissions_;
FineGrainedAccessPermissions fine_grained_access_permissions_;
FineGrainedAccessHandler fine_grained_access_handler_;
std::optional<Role> role_;
};

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

@ -0,0 +1,4 @@
set(mg_glue_sources auth.cpp auth_checker.cpp communication.cpp)
add_library(mg-glue STATIC ${mg_glue_sources})
target_link_libraries(mg-glue mg-query mg-auth)

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

@ -0,0 +1,110 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include "glue/auth_checker.hpp"
#include "auth/auth.hpp"
#include "auth/models.hpp"
#include "glue/auth.hpp"
#include "query/frontend/ast/ast.hpp"
#include "utils/synchronized.hpp"
namespace {
bool IsUserAuthorizedLabels(const memgraph::auth::User &user, const memgraph::query::DbAccessor &dba,
const std::vector<memgraph::storage::LabelId> &labels) {
return std::all_of(labels.begin(), labels.end(), [&dba, &user](const auto label) {
return user.GetFineGrainedAccessLabelPermissions().Has(dba.LabelToName(label)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
bool IsUserAuthorizedEdgeType(const memgraph::auth::User &user, const memgraph::query::DbAccessor &dba,
const memgraph::storage::EdgeTypeId &edgeType) {
return user.GetFineGrainedAccessEdgeTypePermissions().Has(dba.EdgeTypeToName(edgeType)) ==
memgraph::auth::PermissionLevel::GRANT;
}
} // namespace
namespace memgraph::glue {
AuthChecker::AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: auth_(auth) {}
bool AuthChecker::IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const {
std::optional<memgraph::auth::User> maybe_user;
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> AuthChecker::GetFineGrainedAuthChecker(
const std::string &username) const {
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
return std::make_unique<memgraph::glue::FineGrainedAuthChecker>(std::move(*user));
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
bool AuthChecker::IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const auto user_permissions = user.GetPermissions();
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
FineGrainedAuthChecker::FineGrainedAuthChecker(auth::User user) : user_{std::move(user)} {};
bool FineGrainedAuthChecker::Accept(const memgraph::query::DbAccessor &dba,
const memgraph::query::VertexAccessor &vertex,
const memgraph::storage::View &view) const {
auto maybe_labels = vertex.Labels(view);
if (maybe_labels.HasError()) {
switch (maybe_labels.GetError()) {
case memgraph::storage::Error::DELETED_OBJECT:
throw memgraph::query::QueryRuntimeException("Trying to get labels from a deleted node.");
case memgraph::storage::Error::NONEXISTENT_OBJECT:
throw memgraph::query::QueryRuntimeException("Trying to get labels from a node that doesn't exist.");
case memgraph::storage::Error::SERIALIZATION_ERROR:
case memgraph::storage::Error::VERTEX_HAS_EDGES:
case memgraph::storage::Error::PROPERTIES_DISABLED:
throw memgraph::query::QueryRuntimeException("Unexpected error when getting labels.");
}
}
return IsUserAuthorizedLabels(user_, dba, *maybe_labels);
}
bool FineGrainedAuthChecker::Accept(const memgraph::query::DbAccessor &dba,
const memgraph::query::EdgeAccessor &edge) const {
return IsUserAuthorizedEdgeType(user_, dba, edge.EdgeType());
}
} // namespace memgraph::glue

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

@ -0,0 +1,54 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#pragma once
#include "auth/auth.hpp"
#include "auth/models.hpp"
#include "glue/auth.hpp"
#include "query/auth_checker.hpp"
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
namespace memgraph::glue {
class AuthChecker : public query::AuthChecker {
public:
explicit AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth);
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const override;
std::unique_ptr<memgraph::query::FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string &username) const override;
[[nodiscard]] static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges);
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};
class FineGrainedAuthChecker : public query::FineGrainedAuthChecker {
public:
explicit FineGrainedAuthChecker(auth::User user);
virtual bool Accept(const memgraph::query::DbAccessor &dba, const query::VertexAccessor &vertex,
const memgraph::storage::View &view) const override;
virtual bool Accept(const memgraph::query::DbAccessor &dba, const query::EdgeAccessor &edge) const override;
private:
auth::User user_;
};
} // namespace memgraph::glue

View File

@ -19,6 +19,7 @@
#include <functional>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <regex>
#include <string>
@ -32,9 +33,11 @@
#include <spdlog/sinks/dist_sink.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include "auth/models.hpp"
#include "communication/bolt/v1/constants.hpp"
#include "communication/websocket/auth.hpp"
#include "communication/websocket/server.hpp"
#include "glue/auth_checker.hpp"
#include "helpers.hpp"
#include "py/py.hpp"
#include "query/auth_checker.hpp"
@ -506,7 +509,7 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
if (first_user) {
spdlog::info("{} is first created user. Granting all privileges.", username);
GrantPrivilege(username, memgraph::query::kPrivilegesAll, {"*"});
GrantPrivilege(username, memgraph::query::kPrivilegesAll, {"*"}, {"*"});
}
return user_added;
@ -751,28 +754,10 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
}
}
memgraph::auth::User *GetUser(const std::string &username) override {
if (!std::regex_match(username, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user name.");
}
try {
auto locked_auth = auth_->Lock();
auto user = locked_auth->GetUser(username);
if (!user) {
throw memgraph::query::QueryRuntimeException("User '{}' doesn't exist .", username);
}
return new memgraph::auth::User(*user);
} catch (const memgraph::auth::AuthException &e) {
throw memgraph::query::QueryRuntimeException(e.what());
}
}
void GrantPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) override {
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
EditPermissions(user_or_role, privileges, labels, edgeTypes, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
@ -782,8 +767,8 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) override {
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
EditPermissions(user_or_role, privileges, labels, edgeTypes, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
@ -793,8 +778,8 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
void RevokePrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) override {
EditPermissions(user_or_role, privileges, labels, [](auto *permissions, const auto &permission) {
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) override {
EditPermissions(user_or_role, privileges, labels, edgeTypes, [](auto *permissions, const auto &permission) {
// TODO (mferencevic): should we first check that the
// privilege is granted/denied/revoked before
// unconditionally granting/denying/revoking it?
@ -806,7 +791,8 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
template <class TEditFun>
void EditPermissions(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels, const TEditFun &edit_fun) {
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes,
const TEditFun &edit_fun) {
if (!std::regex_match(user_or_role, name_regex_)) {
throw memgraph::query::QueryRuntimeException("Invalid user or role name.");
}
@ -827,15 +813,22 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
edit_fun(&user->permissions(), permission);
}
for (const auto &label : labels) {
edit_fun(&user->fine_grained_access_permissions(), label);
edit_fun(&user->fine_grained_access_handler().label_permissions(), label);
}
for (const auto &edgeType : edgeTypes) {
edit_fun(&user->fine_grained_access_handler().edge_type_permissions(), edgeType);
}
locked_auth->SaveUser(*user);
} else {
for (const auto &permission : permissions) {
edit_fun(&role->permissions(), permission);
}
for (const auto &label : labels) {
edit_fun(&user->fine_grained_access_permissions(), label);
edit_fun(&user->fine_grained_access_handler().label_permissions(), label);
}
for (const auto &edgeType : edgeTypes) {
edit_fun(&role->fine_grained_access_handler().edge_type_permissions(), edgeType);
}
locked_auth->SaveRole(*role);
@ -846,41 +839,6 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
}
};
class AuthChecker final : public memgraph::query::AuthChecker {
public:
explicit AuthChecker(
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth)
: auth_{auth} {}
static bool IsUserAuthorized(const memgraph::auth::User &user,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) {
const auto user_permissions = user.GetPermissions();
return std::all_of(privileges.begin(), privileges.end(), [&user_permissions](const auto privilege) {
return user_permissions.Has(memgraph::glue::PrivilegeToPermission(privilege)) ==
memgraph::auth::PermissionLevel::GRANT;
});
}
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) const final {
std::optional<memgraph::auth::User> maybe_user;
{
auto locked_auth = auth_->ReadLock();
if (!locked_auth->HasUsers()) {
return true;
}
if (username.has_value()) {
maybe_user = locked_auth->GetUser(*username);
}
}
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};
class BoltSession final : public memgraph::communication::bolt::Session<memgraph::communication::v2::InputStream,
memgraph::communication::v2::OutputStream> {
public:
@ -923,7 +881,7 @@ class BoltSession final : public memgraph::communication::bolt::Session<memgraph
#endif
try {
auto result = interpreter_.Prepare(query, params_pv, username);
if (user_ && !AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
if (user_ && !memgraph::glue::AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
interpreter_.Abort();
throw memgraph::communication::bolt::ClientError(
"You are not authorized to execute this query! Please contact "
@ -1272,7 +1230,7 @@ int main(int argc, char **argv) {
memgraph::query::procedure::gModuleRegistry.UnloadAndLoadModulesFromDirectories();
AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
AuthChecker auth_checker{&auth};
memgraph::glue::AuthChecker auth_checker{&auth};
interpreter_context.auth = &auth_handler;
interpreter_context.auth_checker = &auth_checker;

View File

@ -11,19 +11,56 @@
#pragma once
#include "query/db_accessor.hpp"
#include "query/frontend/ast/ast.hpp"
namespace memgraph::query {
class FineGrainedAuthChecker;
class AuthChecker {
public:
virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
virtual ~AuthChecker() = default;
[[nodiscard]] virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
[[nodiscard]] virtual std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(
const std::string &username) const = 0;
};
class AllowEverythingAuthChecker final : public query::AuthChecker {
bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const override {
class FineGrainedAuthChecker {
public:
virtual ~FineGrainedAuthChecker() = default;
[[nodiscard]] virtual bool Accept(const memgraph::query::DbAccessor &dba, const query::VertexAccessor &vertex,
const memgraph::storage::View &view) const = 0;
[[nodiscard]] virtual bool Accept(const memgraph::query::DbAccessor &dba, const query::EdgeAccessor &edge) const = 0;
};
class AllowEverythingUserBasedAuthChecker final : public query::FineGrainedAuthChecker {
public:
bool Accept(const memgraph::query::DbAccessor &dba, const VertexAccessor &vertex,
const memgraph::storage::View &view) const override {
return true;
}
bool Accept(const memgraph::query::DbAccessor &dba, const memgraph::query::EdgeAccessor &edge) const override {
return true;
}
};
} // namespace memgraph::query
class AllowEverythingAuthChecker final : public query::AuthChecker {
public:
bool IsUserAuthorized(const std::optional<std::string> & /*username*/,
const std::vector<query::AuthQuery::Privilege> & /*privileges*/) const override {
return true;
}
std::unique_ptr<FineGrainedAuthChecker> GetFineGrainedAuthChecker(const std::string & /*username*/) const override {
return std::make_unique<AllowEverythingUserBasedAuthChecker>();
}
};
} // namespace memgraph::query

View File

@ -11,10 +11,10 @@
#pragma once
#include <memory>
#include <type_traits>
#include "query/common.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/metadata.hpp"
#include "query/parameters.hpp"
@ -73,7 +73,7 @@ struct ExecutionContext {
ExecutionStats execution_stats;
TriggerContextCollector *trigger_context_collector{nullptr};
utils::AsyncTimer timer;
FineGrainedAccessChecker *fine_grained_access_checker{nullptr};
std::unique_ptr<FineGrainedAuthChecker> auth_checker{nullptr};
};
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");

View File

@ -1,24 +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.
#pragma once
#include "auth/models.hpp"
#include "query/frontend/ast/ast.hpp"
#include "storage/v2/id_types.hpp"
namespace memgraph::query {
class FineGrainedAccessChecker {
public:
virtual bool IsUserAuthorizedLabels(const std::vector<memgraph::storage::LabelId> &label,
const memgraph::query::DbAccessor *dba) const = 0;
};
} // namespace memgraph::query

View File

@ -2244,7 +2244,8 @@ cpp<#
:slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression"))
(privileges "std::vector<Privilege>" :scope :public)
(labels "std::vector<std::string>" :scope :public))
(labels "std::vector<std::string>" :scope :public)
(edge-types "std::vector<std::string>" :scope :public))
(:public
(lcp:define-enum action
(create-role drop-role show-roles create-user set-password drop-user
@ -2266,14 +2267,16 @@ cpp<#
#>cpp
AuthQuery(Action action, std::string user, std::string role,
std::string user_or_role, Expression *password,
std::vector<Privilege> privileges, std::vector<std::string> labels)
std::vector<Privilege> privileges, std::vector<std::string> labels,
std::vector<std::string> edge_types)
: action_(action),
user_(user),
role_(role),
user_or_role_(user_or_role),
password_(password),
privileges_(privileges),
labels_(labels) {}
labels_(labels),
edge_types_(edge_types) {}
cpp<#)
(:private
#>cpp

View File

@ -10,6 +10,7 @@
// licenses/APL.txt.
#include "query/frontend/ast/cypher_main_visitor.hpp"
#include <support/Any.h>
#include <algorithm>
#include <climits>
@ -1268,6 +1269,17 @@ antlrcpp::Any CypherMainVisitor::visitClearRole(MemgraphCypher::ClearRoleContext
return auth;
}
void CypherMainVisitor::extractPrivilege(AuthQuery *auth,
antlropencypher::MemgraphCypher::PrivilegeContext *privilege) {
if (privilege->EDGE_TYPES()) {
auth->edge_types_ = std::any_cast<std::vector<std::string>>(privilege->edgeTypeList()->accept(this));
} else if (privilege->LABELS()) {
auth->labels_ = std::any_cast<std::vector<std::string>>(privilege->labelList()->accept(this));
} else {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
}
/**
* @return AuthQuery*
*/
@ -1277,11 +1289,7 @@ antlrcpp::Any CypherMainVisitor::visitGrantPrivilege(MemgraphCypher::GrantPrivil
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
if (privilege->LABELS()) {
auth->labels_ = std::any_cast<std::vector<std::string>>(privilege->labelList()->accept(this));
} else {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
extractPrivilege(auth, privilege);
}
} else {
/* grant all privileges */
@ -1299,11 +1307,7 @@ antlrcpp::Any CypherMainVisitor::visitDenyPrivilege(MemgraphCypher::DenyPrivileg
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
if (privilege->LABELS()) {
auth->labels_ = std::any_cast<std::vector<std::string>>(privilege->labelList()->accept(this));
} else {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
extractPrivilege(auth, privilege);
}
} else {
/* deny all privileges */
@ -1321,11 +1325,7 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(MemgraphCypher::RevokePriv
auth->user_or_role_ = std::any_cast<std::string>(ctx->userOrRole->accept(this));
if (ctx->privilegeList()) {
for (auto *privilege : ctx->privilegeList()->privilege()) {
if (privilege->LABELS()) {
auth->labels_ = std::any_cast<std::vector<std::string>>(privilege->labelList()->accept(this));
} else {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
}
extractPrivilege(auth, privilege);
}
} else {
/* revoke all privileges */
@ -1347,6 +1347,22 @@ antlrcpp::Any CypherMainVisitor::visitLabelList(MemgraphCypher::LabelListContext
return labels;
}
/**
* @return AuthQuery*
*/
antlrcpp::Any CypherMainVisitor::visitEdgeTypeList(MemgraphCypher::EdgeTypeListContext *ctx) {
std::vector<std::string> edgeTypes;
if (ctx->listOfEdgeTypes()) {
for (auto *edgeType : ctx->listOfEdgeTypes()->edgeType()) {
edgeTypes.push_back(std::any_cast<std::string>(edgeType->symbolicName()->accept(this)));
}
} else {
edgeTypes.emplace_back("*");
}
return edgeTypes;
}
/**
* @return AuthQuery::Privilege
*/

View File

@ -453,6 +453,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitClearRole(MemgraphCypher::ClearRoleContext *ctx) override;
void extractPrivilege(AuthQuery *auth, antlropencypher::MemgraphCypher::PrivilegeContext *privilege);
/**
* @return AuthQuery*
*/
@ -468,6 +470,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitRevokePrivilege(MemgraphCypher::RevokePrivilegeContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitEdgeTypeList(MemgraphCypher::EdgeTypeListContext *ctx) override;
/**
* @return AuthQuery::Privilege
*/

View File

@ -45,6 +45,7 @@ memgraphCypherKeyword : cypherKeyword
| DENY
| DROP
| DUMP
| EDGE_TYPES
| EXECUTE
| FOR
| FOREACH
@ -255,11 +256,18 @@ privilege : CREATE
| MODULE_READ
| MODULE_WRITE
| WEBSOCKET
| EDGE_TYPES edgeTypes=edgeTypeList
| LABELS labels=labelList
;
privilegeList : privilege ( ',' privilege )* ;
edgeTypeList : '*' | listOfEdgeTypes ;
listOfEdgeTypes : edgeType ( ',' edgeType )* ;
edgeType : COLON symbolicName ;
labelList : '*' | listOfLabels ;
listOfLabels : label ( ',' label )* ;

View File

@ -115,3 +115,4 @@ USER : U S E R ;
USERS : U S E R S ;
VERSION : V E R S I O N ;
WEBSOCKET : W E B S O C K E T ;
EDGE_TYPES : E D G E UNDERSCORE T Y P E S ;

View File

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

View File

@ -29,7 +29,6 @@
#include "query/db_accessor.hpp"
#include "query/dump.hpp"
#include "query/exceptions.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/ast/ast_visitor.hpp"
#include "query/frontend/ast/cypher_main_visitor.hpp"
@ -44,8 +43,9 @@
#include "query/stream/common.hpp"
#include "query/trigger.hpp"
#include "query/typed_value.hpp"
#include "storage/v2/edge.hpp"
#include "storage/v2/id_types.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/replication/enums.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
@ -261,23 +261,6 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
storage::Storage *db_;
};
class FineGrainedAccessChecker final : public memgraph::query::FineGrainedAccessChecker {
public:
explicit FineGrainedAccessChecker(memgraph::auth::User *user) : user_{user} {}
bool IsUserAuthorizedLabels(const std::vector<memgraph::storage::LabelId> &labels,
const memgraph::query::DbAccessor *dba) const final {
auto labelPermissions = user_->GetFineGrainedAccessPermissions();
return std::any_of(labels.begin(), labels.end(), [&labelPermissions, dba](const auto label) {
return labelPermissions.Has(dba->LabelToName(label)) == memgraph::auth::PermissionLevel::GRANT;
});
}
private:
memgraph::auth::User *user_;
};
/// returns false if the replication role can't be set
/// @throw QueryRuntimeException if an error ocurred.
@ -292,6 +275,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
// the argument to Callback.
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::View::OLD);
@ -299,6 +283,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
std::string rolename = auth_query->role_;
std::string user_or_role = auth_query->user_or_role_;
std::vector<AuthQuery::Privilege> privileges = auth_query->privileges_;
std::vector<std::string> edgeTypes = auth_query->edge_types_;
std::vector<std::string> labels = auth_query->labels_;
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
@ -329,7 +314,7 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
// If the license is not valid we create users with admin access
if (!valid_enterprise_license) {
spdlog::warn("Granting all the privileges to {}.", username);
auth->GrantPrivilege(username, kPrivilegesAll, {"*"});
auth->GrantPrivilege(username, kPrivilegesAll, {"*"}, {"*"});
}
return std::vector<std::vector<TypedValue>>();
@ -404,20 +389,20 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
};
return callback;
case AuthQuery::Action::GRANT_PRIVILEGE:
callback.fn = [auth, user_or_role, privileges, labels] {
auth->GrantPrivilege(user_or_role, privileges, labels);
callback.fn = [auth, user_or_role, privileges, labels, edgeTypes] {
auth->GrantPrivilege(user_or_role, privileges, labels, edgeTypes);
return std::vector<std::vector<TypedValue>>();
};
return callback;
case AuthQuery::Action::DENY_PRIVILEGE:
callback.fn = [auth, user_or_role, privileges, labels] {
auth->DenyPrivilege(user_or_role, privileges, labels);
callback.fn = [auth, user_or_role, privileges, labels, edgeTypes] {
auth->DenyPrivilege(user_or_role, privileges, labels, edgeTypes);
return std::vector<std::vector<TypedValue>>();
};
return callback;
case AuthQuery::Action::REVOKE_PRIVILEGE: {
callback.fn = [auth, user_or_role, privileges, labels] {
auth->RevokePrivilege(user_or_role, privileges, labels);
callback.fn = [auth, user_or_role, privileges, labels, edgeTypes] {
auth->RevokePrivilege(user_or_role, privileges, labels, edgeTypes);
return std::vector<std::vector<TypedValue>>();
};
return callback;
@ -960,8 +945,7 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, dba);
#ifdef MG_ENTERPRISE
if (username.has_value()) {
memgraph::auth::User *user = interpreter_context->auth->GetUser(*username);
ctx_.fine_grained_access_checker = new FineGrainedAccessChecker{user};
ctx_.auth_checker = interpreter_context->auth_checker->GetFineGrainedAuthChecker(*username);
}
#endif
if (interpreter_context->config.execution_timeout_sec > 0) {
@ -1146,6 +1130,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
EvaluationContext evaluation_context;
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parsed_query.parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::View::OLD);
const auto memory_limit = EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
if (memory_limit) {

View File

@ -13,7 +13,6 @@
#include <gflags/gflags.h>
#include "auth/models.hpp"
#include "query/auth_checker.hpp"
#include "query/config.hpp"
#include "query/context.hpp"
@ -99,20 +98,17 @@ class AuthQueryHandler {
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual memgraph::auth::User *GetUser(const std::string &username) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) = 0;
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) = 0;
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void RevokePrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) = 0;
const std::vector<std::string> &labels, const std::vector<std::string> &edgeTypes) = 0;
};
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };
@ -179,6 +175,13 @@ struct InterpreterContext {
storage::Storage *db;
// ANTLR has singleton instance that is shared between threads. It is
// protected by locks inside of ANTLR. Unfortunately, they are not protected
// in a very good way. Once we have ANTLR version without race conditions we
// can remove this lock. This will probably never happen since ANTLR
// developers introduce more bugs in each version. Fortunately, we have
// cache so this lock probably won't impact performance much...
utils::SpinLock antlr_lock;
std::optional<double> tsc_frequency{utils::GetTSCFrequency()};
std::atomic<bool> is_shutting_down{false};

View File

@ -29,7 +29,6 @@
#include "query/context.hpp"
#include "query/db_accessor.hpp"
#include "query/exceptions.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/interpret/eval.hpp"
@ -39,6 +38,7 @@
#include "query/procedure/mg_procedure_impl.hpp"
#include "query/procedure/module.hpp"
#include "storage/v2/property_value.hpp"
#include "storage/v2/view.hpp"
#include "utils/algorithm.hpp"
#include "utils/csv_parsing.hpp"
#include "utils/event_counter.hpp"
@ -407,7 +407,7 @@ class ScanAllCursor : public Cursor {
}
#ifdef MG_ENTERPRISE
if (context.fine_grained_access_checker && !FindNextVertex(context)) {
if (context.auth_checker && !FindNextVertex(context)) {
return false;
}
#endif
@ -419,8 +419,7 @@ class ScanAllCursor : public Cursor {
bool FindNextVertex(const ExecutionContext &context) {
while (vertices_it_.value() != vertices_.value().end()) {
if (context.fine_grained_access_checker->IsUserAuthorizedLabels(
(*vertices_it_.value()).Labels(memgraph::storage::View::NEW).GetValue(), context.db_accessor)) {
if (context.auth_checker->Accept(*context.db_accessor, *vertices_it_.value(), memgraph::storage::View::OLD)) {
return true;
}
++vertices_it_.value();
@ -702,6 +701,11 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
auto edge = *(*in_edges_it_)++;
if (context.auth_checker && (!context.auth_checker->Accept(*context.db_accessor, edge) ||
!context.auth_checker->Accept(*context.db_accessor, edge.From(), self_.view_))) {
continue;
}
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::IN);
return true;
@ -714,6 +718,11 @@ bool Expand::ExpandCursor::Pull(Frame &frame, ExecutionContext &context) {
// we should do only one expansion for cycles, and it was
// already done in the block above
if (self_.common_.direction == EdgeAtom::Direction::BOTH && edge.IsCycle()) continue;
if (context.auth_checker && (!context.auth_checker->Accept(*context.db_accessor, edge) ||
!context.auth_checker->Accept(*context.db_accessor, edge.To(), self_.view_))) {
continue;
}
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
@ -851,6 +860,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio
chain_elements.emplace_back(wrapper(EdgeAtom::Direction::IN, std::move(edges)));
}
}
if (direction != EdgeAtom::Direction::IN) {
auto edges = UnwrapEdgesResult(vertex.OutEdges(view, edge_types));
if (edges.begin() != edges.end()) {
@ -1381,6 +1391,7 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
const auto &vertex = vertex_value.ValueVertex();
processed_.emplace(vertex, std::nullopt);
expand_from_vertex(vertex);
// go back to loop start and see if we expanded anything

View File

@ -2252,19 +2252,12 @@ mgp_error mgp_graph_delete_edge(struct mgp_graph *graph, mgp_edge *edge) {
namespace {
void NextPermitted(mgp_vertices_iterator &it) {
const auto *checker = it.graph->ctx->fine_grained_access_checker;
if (!checker) {
if (!it.graph->ctx->auth_checker) {
return;
}
while (it.current_it != it.vertices.end()) {
auto labels = (*it.current_it).impl_.Labels(it.graph->view);
if (!labels.HasValue()) {
break;
}
if (checker->IsUserAuthorizedLabels(labels.GetValue(), it.graph->ctx->db_accessor)) {
if (it.graph->ctx->auth_checker->Accept(*it.graph->ctx->db_accessor, *it.current_it, it.graph->view)) {
break;
}

View File

@ -29,6 +29,7 @@ function(copy_e2e_cpp_files TARGET_PREFIX FILE_NAME)
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FILE_NAME})
endfunction()
add_subdirectory(fine_grained_access)
add_subdirectory(server)
add_subdirectory(replication)
add_subdirectory(memory)

View File

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

View File

@ -0,0 +1,22 @@
# Copyright 2021 Memgraph Ltd.
#
# Use of this software is governed by the Business Source License
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
# License, and you may not use this file except in compliance with the Business Source License.
#
# As of the Change Date specified in that file, in accordance with
# the Business Source License, use of this software will be governed
# by the Apache License, Version 2.0, included in the file
# licenses/APL.txt.
import mgclient
def execute_and_fetch_all(cursor, query):
cursor.execute(query)
return cursor.fetchall()
def connect(**kwargs):
connection = mgclient.connect(host="localhost", port=7687, **kwargs)
connection.autocommit = True
return connection

View File

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

View File

@ -0,0 +1,27 @@
bolt_port: &bolt_port "7687"
template_cluster: &template_cluster
cluster:
main:
args: ["--bolt-port", *bolt_port, "--log-level=TRACE"]
log_file: "fine_grained_access.log"
setup_queries:
[
"CREATE USER admin IDENTIFIED BY 'test';",
"CREATE USER user IDENTIFIED BY 'test';",
"GRANT ALL PRIVILEGES TO admin;",
"GRANT ALL PRIVILEGES TO user;",
"MERGE (l1:label1 {name: 'test1'});",
"MERGE (l2:label2 {name: 'test2'});",
"MATCH (l1:label1),(l2:label2) WHERE l1.name = 'test1' AND l2.name = 'test2' CREATE (l1)-[r:edgeType1]->(l2);",
"MERGE (l3:label3 {name: 'test3'});",
"MATCH (l1:label1),(l3:label3) WHERE l1.name = 'test1' AND l3.name = 'test3' CREATE (l1)-[r:edgeType2]->(l3);",
"MERGE (mix:label3:label1 {name: 'test4'});",
"MATCH (l1:label1),(mix:label3) WHERE l1.name = 'test1' AND mix.name = 'test4' CREATE (l1)-[r:edgeType2]->(mix);",
]
validation_queries: []
workloads:
- name: "Fine Grained Access"
binary: "tests/e2e/pytest_runner.sh"
args: ["fine_grained_access/edge_type_filtering_tests.py"]
<<: *template_cluster

View File

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

View File

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

View File

@ -0,0 +1,59 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <gflags/gflags.h>
#include <cstdlib>
#include "communication/bolt/client.hpp"
#include "io/network/endpoint.hpp"
#include "io/network/utils.hpp"
#include "utils/logging.hpp"
DEFINE_string(address, "127.0.0.1", "Server address");
DEFINE_int32(port, 7687, "Server port");
DEFINE_string(username, "admin", "Username for the database");
DEFINE_string(password, "admin", "Password for the database");
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
/**
* Verifies that user 'user' has privileges that are given as positional
* arguments.
*/
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
memgraph::communication::SSLInit sslInit;
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
memgraph::communication::ClientContext context(FLAGS_use_ssl);
memgraph::communication::bolt::Client client(&context);
client.Connect(endpoint, FLAGS_username, FLAGS_password);
try {
std::string query(argv[1]);
auto ret = client.Execute(query, {});
uint64_t count_got = ret.records.size();
if (count_got != std::atoi(argv[2])) {
LOG_FATAL("Expected the record to have {} entries but they had {} entries!", argv[2], count_got);
}
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
LOG_FATAL(
"The query shoudn't have failed but it failed with an "
"error message '{}', {}",
e.what(), argv[0]);
}
return 0;
}

View File

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

View File

@ -0,0 +1,84 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <gflags/gflags.h>
#include "communication/bolt/client.hpp"
#include "io/network/endpoint.hpp"
#include "io/network/utils.hpp"
DEFINE_string(address, "127.0.0.1", "Server address");
DEFINE_int32(port, 7687, "Server port");
DEFINE_string(username, "", "Username for the database");
DEFINE_string(password, "", "Password for the database");
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
DEFINE_string(failure_message, "", "Set to the expected failure message.");
/**
* Executes queries passed as positional arguments and verifies whether they
* succeeded, failed, failed with a specific error message or executed without a
* specific error occurring.
*/
int main(int argc, char **argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
memgraph::communication::SSLInit sslInit;
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
memgraph::communication::ClientContext context(FLAGS_use_ssl);
memgraph::communication::bolt::Client client(&context);
client.Connect(endpoint, FLAGS_username, FLAGS_password);
for (int i = 1; i < argc; ++i) {
std::string query(argv[i]);
try {
client.Execute(query, {});
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
if (!FLAGS_check_failure) {
if (!FLAGS_failure_message.empty() && e.what() == FLAGS_failure_message) {
LOG_FATAL(
"The query should have succeeded or failed with an error "
"message that isn't equal to '{}' but it failed with that error "
"message",
FLAGS_failure_message);
}
continue;
}
if (FLAGS_should_fail) {
if (!FLAGS_failure_message.empty() && e.what() != FLAGS_failure_message) {
LOG_FATAL(
"The query should have failed with an error message of '{}'' but "
"instead it failed with '{}'",
FLAGS_failure_message, e.what());
}
return 0;
} else {
LOG_FATAL(
"The query shoudn't have failed but it failed with an "
"error message '{}'",
e.what());
}
}
if (!FLAGS_check_failure) continue;
if (FLAGS_should_fail) {
LOG_FATAL(
"The query should have failed but instead it executed "
"successfully!");
}
}
return 0;
}

View File

@ -76,7 +76,7 @@ add_unit_test(cypher_main_visitor.cpp)
target_link_libraries(${test_prefix}cypher_main_visitor mg-query)
add_unit_test(interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/communication.cpp)
target_link_libraries(${test_prefix}interpreter mg-communication mg-query)
target_link_libraries(${test_prefix}interpreter mg-communication mg-query mg-glue)
add_unit_test(plan_pretty_print.cpp)
target_link_libraries(${test_prefix}plan_pretty_print mg-query)
@ -94,32 +94,32 @@ add_unit_test(query_plan.cpp)
target_link_libraries(${test_prefix}query_plan mg-query)
add_unit_test(query_plan_accumulate_aggregate.cpp)
target_link_libraries(${test_prefix}query_plan_accumulate_aggregate mg-query)
target_link_libraries(${test_prefix}query_plan_accumulate_aggregate mg-query mg-glue)
add_unit_test(query_plan_bag_semantics.cpp)
target_link_libraries(${test_prefix}query_plan_bag_semantics mg-query)
target_link_libraries(${test_prefix}query_plan_bag_semantics mg-query mg-glue)
add_unit_test(query_plan_create_set_remove_delete.cpp)
target_link_libraries(${test_prefix}query_plan_create_set_remove_delete mg-query)
target_link_libraries(${test_prefix}query_plan_create_set_remove_delete mg-query mg-glue)
add_unit_test(query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/communication.cpp)
target_link_libraries(${test_prefix}query_plan_edge_cases mg-communication mg-query)
add_unit_test(query_plan_match_filter_return.cpp)
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query)
target_link_libraries(${test_prefix}query_plan_match_filter_return mg-query mg-query mg-glue)
add_unit_test(query_plan_read_write_typecheck.cpp
${CMAKE_SOURCE_DIR}/src/query/plan/read_write_type_checker.cpp)
target_link_libraries(${test_prefix}query_plan_read_write_typecheck mg-query)
add_unit_test(query_plan_v2_create_set_remove_delete.cpp)
target_link_libraries(${test_prefix}query_plan_v2_create_set_remove_delete mg-query)
target_link_libraries(${test_prefix}query_plan_v2_create_set_remove_delete mg-query mg-glue)
add_unit_test(query_pretty_print.cpp)
target_link_libraries(${test_prefix}query_pretty_print mg-query)
add_unit_test(query_trigger.cpp)
target_link_libraries(${test_prefix}query_trigger mg-query)
target_link_libraries(${test_prefix}query_trigger mg-query mg-glue)
add_unit_test(query_serialization_property_value.cpp)
target_link_libraries(${test_prefix}query_serialization_property_value mg-query)
@ -160,7 +160,7 @@ add_unit_test(query_semantic.cpp)
target_link_libraries(${test_prefix}query_semantic mg-query)
add_unit_test(query_variable_start_planner.cpp)
target_link_libraries(${test_prefix}query_variable_start_planner mg-query)
target_link_libraries(${test_prefix}query_variable_start_planner mg-query mg-glue)
add_unit_test(stripped.cpp)
target_link_libraries(${test_prefix}stripped mg-query)
@ -321,6 +321,9 @@ target_link_libraries(${test_prefix}storage_v2_isolation_level mg-storage-v2)
add_unit_test(replication_persistence_helper.cpp)
target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2)
add_unit_test(auth_checker.cpp)
target_link_libraries(${test_prefix}auth_checker mg-glue mg-auth)
# Test mg-auth
if(MG_ENTERPRISE)
add_unit_test(auth.cpp)

View File

@ -17,6 +17,7 @@
#include "auth/auth.hpp"
#include "auth/crypto.hpp"
#include "auth/models.hpp"
#include "utils/cast.hpp"
#include "utils/file.hpp"
#include "utils/license.hpp"
@ -159,6 +160,73 @@ TEST_F(AuthWithStorage, UserRolePermissions) {
}
}
TEST_F(AuthWithStorage, UserRoleFineGrainedAccessHandler) {
ASSERT_FALSE(auth.HasUsers());
ASSERT_TRUE(auth.AddUser("test"));
ASSERT_TRUE(auth.HasUsers());
auto user = auth.GetUser("test");
ASSERT_NE(user, std::nullopt);
// Test initial user fine grained access permissions.
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), FineGrainedAccessPermissions{});
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(), FineGrainedAccessPermissions{});
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
user->GetFineGrainedAccessEdgeTypePermissions());
// Grant one label to user .
user->fine_grained_access_handler().label_permissions().Grant("labelTest");
// Grant one edge type to user .
user->fine_grained_access_handler().edge_type_permissions().Grant("edgeTypeTest");
// Check permissions.
ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest"), PermissionLevel::GRANT);
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest"), PermissionLevel::GRANT);
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
user->GetFineGrainedAccessEdgeTypePermissions());
// Deny one label to user .
user->fine_grained_access_handler().label_permissions().Deny("labelTest1");
// Deny one edge type to user .
user->fine_grained_access_handler().edge_type_permissions().Deny("edgeTypeTest1");
// Check permissions.
ASSERT_EQ(user->fine_grained_access_handler().label_permissions().Has("labelTest1"), PermissionLevel::DENY);
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions().Has("edgeTypeTest1"), PermissionLevel::DENY);
ASSERT_EQ(user->fine_grained_access_handler().label_permissions(), user->GetFineGrainedAccessLabelPermissions());
ASSERT_EQ(user->fine_grained_access_handler().edge_type_permissions(),
user->GetFineGrainedAccessEdgeTypePermissions());
// Create role.
ASSERT_TRUE(auth.AddRole("admin"));
auto role = auth.GetRole("admin");
ASSERT_NE(role, std::nullopt);
// Grant label and edge type to role and role to user.
role->fine_grained_access_handler().label_permissions().Grant("roleLabelTest");
role->fine_grained_access_handler().edge_type_permissions().Grant("roleEdgeTypeTest");
user->SetRole(*role);
// Check permissions.
{
ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest"), PermissionLevel::GRANT);
ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest"), PermissionLevel::GRANT);
}
// Deny label and edge type to role and role to user.
role->fine_grained_access_handler().label_permissions().Deny("roleLabelTest1");
role->fine_grained_access_handler().edge_type_permissions().Deny("roleEdgeTypeTest1");
user->SetRole(*role);
// Check permissions.
{
ASSERT_EQ(user->GetFineGrainedAccessLabelPermissions().Has("roleLabelTest1"), PermissionLevel::DENY);
ASSERT_EQ(user->GetFineGrainedAccessEdgeTypePermissions().Has("roleEdgeTypeTest1"), PermissionLevel::DENY);
}
}
TEST_F(AuthWithStorage, RoleManipulations) {
{
auto user1 = auth.AddUser("user1");
@ -468,9 +536,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto fine_grained_perms = FineGrainedAccessPermissions();
auto user1 = User("test", "pw", perms, fine_grained_perms);
auto user2 = User("Test", "pw", perms, fine_grained_perms);
auto fine_grained_access_handler = FineGrainedAccessHandler();
auto user1 = User("test", "pw", perms, fine_grained_access_handler);
auto user2 = User("Test", "pw", perms, fine_grained_access_handler);
ASSERT_EQ(user1, user2);
ASSERT_EQ(user1.username(), user2.username());
ASSERT_EQ(user1.username(), "test");
@ -486,9 +554,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto fine_grained_perms = FineGrainedAccessPermissions();
auto role1 = Role("role", perms, fine_grained_perms);
auto role2 = Role("Role", perms, fine_grained_perms);
auto fine_grained_access_handler = FineGrainedAccessHandler();
auto role1 = Role("role", perms, fine_grained_access_handler);
auto role2 = Role("Role", perms, fine_grained_access_handler);
ASSERT_EQ(role1, role2);
ASSERT_EQ(role1.rolename(), role2.rolename());
ASSERT_EQ(role1.rolename(), "role");

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

@ -0,0 +1,170 @@
// Copyright 2022 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "auth/models.hpp"
#include "glue/auth_checker.hpp"
#include "query_plan_common.hpp"
#include "storage/v2/view.hpp"
class FineGrainedAuthCheckerFixture : public testing::Test {
protected:
memgraph::storage::Storage db;
memgraph::storage::Storage::Accessor storage_dba{db.Access()};
memgraph::query::DbAccessor dba{&storage_dba};
// make a V-graph (v3)<-[r2]-(v1)-[r1]->(v2)
memgraph::query::VertexAccessor v1{dba.InsertVertex()};
memgraph::query::VertexAccessor v2{dba.InsertVertex()};
memgraph::query::VertexAccessor v3{dba.InsertVertex()};
memgraph::storage::EdgeTypeId edge_type_one{db.NameToEdgeType("edge_type_1")};
memgraph::storage::EdgeTypeId edge_type_two{db.NameToEdgeType("edge_type_2")};
memgraph::query::EdgeAccessor r1{*dba.InsertEdge(&v1, &v2, edge_type_one)};
memgraph::query::EdgeAccessor r2{*dba.InsertEdge(&v1, &v3, edge_type_one)};
memgraph::query::EdgeAccessor r3{*dba.InsertEdge(&v1, &v2, edge_type_two)};
memgraph::query::EdgeAccessor r4{*dba.InsertEdge(&v1, &v3, edge_type_two)};
void SetUp() override {
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l1")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l2")).HasValue());
ASSERT_TRUE(v3.AddLabel(dba.NameToLabel("l3")).HasValue());
dba.AdvanceCommand();
}
};
TEST_F(FineGrainedAuthCheckerFixture, GrantedAllLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("*");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::OLD));
ASSERT_TRUE(auth_checker.Accept(dba, v2, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v2, memgraph::storage::View::OLD));
ASSERT_TRUE(auth_checker.Accept(dba, v3, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v3, memgraph::storage::View::OLD));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantedAllEdgeTypes) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant("*");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(auth_checker.Accept(dba, r1));
ASSERT_TRUE(auth_checker.Accept(dba, r2));
ASSERT_TRUE(auth_checker.Accept(dba, r3));
ASSERT_TRUE(auth_checker.Accept(dba, r4));
}
TEST_F(FineGrainedAuthCheckerFixture, DeniedAllLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Deny("*");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_FALSE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
ASSERT_FALSE(auth_checker.Accept(dba, v1, memgraph::storage::View::OLD));
ASSERT_FALSE(auth_checker.Accept(dba, v2, memgraph::storage::View::NEW));
ASSERT_FALSE(auth_checker.Accept(dba, v2, memgraph::storage::View::OLD));
ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::NEW));
ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::OLD));
}
TEST_F(FineGrainedAuthCheckerFixture, DeniedAllEdgeTypes) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Deny("*");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_FALSE(auth_checker.Accept(dba, r1));
ASSERT_FALSE(auth_checker.Accept(dba, r2));
ASSERT_FALSE(auth_checker.Accept(dba, r3));
ASSERT_FALSE(auth_checker.Accept(dba, r4));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantLabel) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l1");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::OLD));
}
TEST_F(FineGrainedAuthCheckerFixture, DenyLabel) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Deny("l3");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::NEW));
ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::OLD));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l1");
user.fine_grained_access_handler().label_permissions().Grant("l2");
user.fine_grained_access_handler().label_permissions().Deny("l3");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v1, memgraph::storage::View::OLD));
ASSERT_TRUE(auth_checker.Accept(dba, v2, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v2, memgraph::storage::View::OLD));
ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::NEW));
ASSERT_FALSE(auth_checker.Accept(dba, v3, memgraph::storage::View::OLD));
}
TEST_F(FineGrainedAuthCheckerFixture, MultipleVertexLabels) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().label_permissions().Grant("l1");
user.fine_grained_access_handler().label_permissions().Grant("l2");
user.fine_grained_access_handler().label_permissions().Deny("l3");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(v1.AddLabel(dba.NameToLabel("l3")).HasValue());
ASSERT_TRUE(v2.AddLabel(dba.NameToLabel("l1")).HasValue());
dba.AdvanceCommand();
ASSERT_FALSE(auth_checker.Accept(dba, v1, memgraph::storage::View::NEW));
ASSERT_FALSE(auth_checker.Accept(dba, v1, memgraph::storage::View::OLD));
ASSERT_TRUE(auth_checker.Accept(dba, v2, memgraph::storage::View::NEW));
ASSERT_TRUE(auth_checker.Accept(dba, v2, memgraph::storage::View::OLD));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantEdgeType) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(auth_checker.Accept(dba, r1));
}
TEST_F(FineGrainedAuthCheckerFixture, DenyEdgeType) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_1");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_FALSE(auth_checker.Accept(dba, r1));
}
TEST_F(FineGrainedAuthCheckerFixture, GrantAndDenySpecificEdgeTypes) {
memgraph::auth::User user{"test"};
user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_1");
user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_2");
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
ASSERT_TRUE(auth_checker.Accept(dba, r1));
ASSERT_TRUE(auth_checker.Accept(dba, r2));
ASSERT_FALSE(auth_checker.Accept(dba, r3));
ASSERT_FALSE(auth_checker.Accept(dba, r4));
}

View File

@ -583,7 +583,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
#define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__})
#define EXTRACT(variable, list, expr) \
storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr)
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), (labels))
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \
(labels), (edgeTypes))
#define DROP_USER(usernames) storage.Create<memgraph::query::DropUser>((usernames))
#define CALL_PROCEDURE(...) memgraph::query::test_common::GetCallProcedure(storage, __VA_ARGS__)

View File

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

View File

@ -25,11 +25,15 @@
#include <cppitertools/range.hpp>
#include <cppitertools/repeat.hpp>
#include "auth/auth.hpp"
#include "auth/models.hpp"
#include "glue/auth_checker.hpp"
#include "query/context.hpp"
#include "query/exceptions.hpp"
#include "query/plan/operator.hpp"
#include "query_plan_common.hpp"
#include "utils/synchronized.hpp"
using namespace memgraph::query;
using namespace memgraph::query::plan;
@ -407,6 +411,57 @@ TEST_F(ExpandFixture, Expand) {
EXPECT_EQ(8, test_expand(EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD));
}
TEST_F(ExpandFixture, ExpandWithEdgeFiltering) {
auto test_expand = [&](memgraph::auth::User user, EdgeAtom::Direction direction, memgraph::storage::View view) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", direction, {}, "m", false, view);
// make a named expression and a produce
auto output =
NEXPR("m", IDENT("m")->MapTo(r_m.node_sym_))->MapTo(symbol_table.CreateSymbol("named_expression_1", true));
auto produce = MakeProduce(r_m.op_, output);
memgraph::glue::FineGrainedAuthChecker auth_checker{user};
auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker);
return PullAll(*produce, &context);
};
auto user = memgraph::auth::User("test");
user.fine_grained_access_handler().edge_type_permissions().Grant("Edge");
user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_test");
user.fine_grained_access_handler().label_permissions().Grant("*");
memgraph::storage::EdgeTypeId edge_type_test{db.NameToEdgeType("edge_type_test")};
ASSERT_TRUE(dba.InsertEdge(&v1, &v2, edge_type_test).HasValue());
ASSERT_TRUE(dba.InsertEdge(&v1, &v3, edge_type_test).HasValue());
// test that expand works well for both old and new graph state
EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD));
EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD));
EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::NEW));
EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::NEW));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::NEW));
dba.AdvanceCommand();
EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD));
EXPECT_EQ(2, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD));
user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type_test");
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD));
EXPECT_EQ(8, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::NEW));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::NEW));
EXPECT_EQ(8, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::NEW));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::OUT, memgraph::storage::View::OLD));
EXPECT_EQ(4, test_expand(user, EdgeAtom::Direction::IN, memgraph::storage::View::OLD));
EXPECT_EQ(8, test_expand(user, EdgeAtom::Direction::BOTH, memgraph::storage::View::OLD));
}
TEST_F(ExpandFixture, ExpandPath) {
auto n = MakeScanAll(storage, symbol_table, "n");
auto r_m = MakeExpand(storage, symbol_table, n.op_, n.sym_, "r", EdgeAtom::Direction::OUT, {}, "m", false,

View File

@ -99,7 +99,7 @@ TEST_F(TestPrivilegeExtractor, CreateIndex) {
TEST_F(TestPrivilegeExtractor, AuthQuery) {
auto *query = AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "", nullptr, std::vector<AuthQuery::Privilege>{},
std::vector<std::string>{});
std::vector<std::string>{}, std::vector<std::string>{});
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::AUTH));
}

View File

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