[E129-MG <-T0982-MG] implement edge type filtering (#489)
* GRANT, REVOKE, DENY and access_checker DONE * Added AccessChecker to ExecutionContext * grammar expanded; (#462) * current * T0954 mg expand user and role to hold permissions on labels (#465) * 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 (#464) * 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 (#467) * 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:
parent
a98463b0bd
commit
e15576f56c
@ -12,6 +12,7 @@ add_subdirectory(memory)
|
||||
add_subdirectory(storage/v2)
|
||||
add_subdirectory(integrations)
|
||||
add_subdirectory(query)
|
||||
add_subdirectory(glue)
|
||||
add_subdirectory(slk)
|
||||
add_subdirectory(rpc)
|
||||
add_subdirectory(auth)
|
||||
@ -31,13 +32,11 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
# Memgraph Single Node v2 Executable
|
||||
# ----------------------------------------------------------------------------
|
||||
set(mg_single_node_v2_sources
|
||||
glue/communication.cpp
|
||||
memgraph.cpp
|
||||
glue/auth.cpp
|
||||
memgraph.cpp
|
||||
)
|
||||
|
||||
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings)
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth mg-license mg-settings mg-glue)
|
||||
if (MG_ENTERPRISE)
|
||||
# These are enterprise subsystems
|
||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
||||
|
@ -226,7 +226,7 @@ std::vector<auth::User> Auth::AllUsers() const {
|
||||
if (username != utils::ToLowerCase(username)) continue;
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(*user);
|
||||
ret.push_back(std::move(*user));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
@ -306,7 +306,7 @@ std::vector<auth::User> Auth::AllUsersForRole(const std::string &rolename_orig)
|
||||
if (it->second == rolename) {
|
||||
auto user = GetUser(username);
|
||||
if (user) {
|
||||
ret.push_back(*user);
|
||||
ret.push_back(std::move(*user));
|
||||
} else {
|
||||
throw AuthException("Couldn't load user '{}'!", username);
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
namespace memgraph::auth {
|
||||
/**
|
||||
* This class serves as the main Authentication/Authorization storage.
|
||||
* It provides functions for managing Users, Roles and Permissions.
|
||||
* It provides functions for managing Users, Roles, Permissions and FineGrainedAccessPermissions.
|
||||
* NOTE: The non-const functions in this class aren't thread safe.
|
||||
* TODO (mferencevic): Disable user/role modification functions when they are
|
||||
* being managed by the auth module.
|
||||
|
@ -8,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
|
||||
|
@ -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
4
src/glue/CMakeLists.txt
Normal 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
110
src/glue/auth_checker.cpp
Normal 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
54
src/glue/auth_checker.hpp
Normal 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
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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!");
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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 )* ;
|
||||
|
@ -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 ;
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
6
tests/e2e/fine_grained_access/CMakeLists.txt
Normal file
6
tests/e2e/fine_grained_access/CMakeLists.txt
Normal 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)
|
22
tests/e2e/fine_grained_access/common.py
Normal file
22
tests/e2e/fine_grained_access/common.py
Normal 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
|
72
tests/e2e/fine_grained_access/edge_type_filtering_tests.py
Normal file
72
tests/e2e/fine_grained_access/edge_type_filtering_tests.py
Normal 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"]))
|
27
tests/e2e/fine_grained_access/workloads.yaml
Normal file
27
tests/e2e/fine_grained_access/workloads.yaml
Normal 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
|
@ -10,6 +10,9 @@ add_subdirectory(transactions)
|
||||
# auth test binaries
|
||||
add_subdirectory(auth)
|
||||
|
||||
# lba test binaries
|
||||
add_subdirectory(fine_grained_access)
|
||||
|
||||
## distributed ha/basic binaries
|
||||
#add_subdirectory(ha/basic)
|
||||
#
|
||||
|
11
tests/integration/fine_grained_access/CMakeLists.txt
Normal file
11
tests/integration/fine_grained_access/CMakeLists.txt
Normal file
@ -0,0 +1,11 @@
|
||||
set(target_name memgraph__integration__fine_grained_access)
|
||||
set(tester_target_name ${target_name}__tester)
|
||||
set(filtering_target_name ${target_name}__filtering)
|
||||
|
||||
add_executable(${tester_target_name} tester.cpp)
|
||||
set_target_properties(${tester_target_name} PROPERTIES OUTPUT_NAME tester)
|
||||
target_link_libraries(${tester_target_name} mg-communication)
|
||||
|
||||
add_executable(${filtering_target_name} filtering.cpp)
|
||||
set_target_properties(${filtering_target_name} PROPERTIES OUTPUT_NAME filtering)
|
||||
target_link_libraries(${filtering_target_name} mg-communication)
|
59
tests/integration/fine_grained_access/filtering.cpp
Normal file
59
tests/integration/fine_grained_access/filtering.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "admin", "Username for the database");
|
||||
DEFINE_string(password, "admin", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
/**
|
||||
* Verifies that user 'user' has privileges that are given as positional
|
||||
* arguments.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(&context);
|
||||
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
|
||||
try {
|
||||
std::string query(argv[1]);
|
||||
auto ret = client.Execute(query, {});
|
||||
uint64_t count_got = ret.records.size();
|
||||
|
||||
if (count_got != std::atoi(argv[2])) {
|
||||
LOG_FATAL("Expected the record to have {} entries but they had {} entries!", argv[2], count_got);
|
||||
}
|
||||
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}', {}",
|
||||
e.what(), argv[0]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
135
tests/integration/fine_grained_access/runner.py
Normal file
135
tests/integration/fine_grained_access/runner.py
Normal file
@ -0,0 +1,135 @@
|
||||
#!/usr/bin/python3 -u
|
||||
|
||||
# Copyright 2021 Memgraph Ltd.
|
||||
#
|
||||
# Use of this software is governed by the Business Source License
|
||||
# included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
# License, and you may not use this file except in compliance with the Business Source License.
|
||||
#
|
||||
# As of the Change Date specified in that file, in accordance with
|
||||
# the Business Source License, use of this software will be governed
|
||||
# by the Apache License, Version 2.0, included in the file
|
||||
# licenses/APL.txt.
|
||||
|
||||
import argparse
|
||||
import atexit
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PROJECT_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
|
||||
|
||||
UNAUTHORIZED_ERROR = "You are not authorized to execute this query! Please " "contact your database administrator."
|
||||
|
||||
|
||||
def wait_for_server(port, delay=0.1):
|
||||
cmd = ["nc", "-z", "-w", "1", "127.0.0.1", str(port)]
|
||||
while subprocess.call(cmd) != 0:
|
||||
time.sleep(0.01)
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
def execute_tester(
|
||||
binary, queries, should_fail=False, failure_message="", username="", password="", check_failure=True
|
||||
):
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
if should_fail:
|
||||
args.append("--should-fail")
|
||||
if failure_message:
|
||||
args.extend(["--failure-message", failure_message])
|
||||
if check_failure:
|
||||
args.append("--check-failure")
|
||||
args.extend(queries)
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def execute_filtering(binary: str, queries: List[str], expected: int, username: str = "", password: str = "") -> None:
|
||||
args = [binary, "--username", username, "--password", password]
|
||||
|
||||
args.extend(queries)
|
||||
args.append(str(expected))
|
||||
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
|
||||
def execute_test(memgraph_binary: str, tester_binary: str, filtering_binary: str) -> None:
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [memgraph_binary, "--data-directory", storage_directory.name]
|
||||
|
||||
def execute_admin_queries(queries):
|
||||
return execute_tester(
|
||||
tester_binary, queries, should_fail=False, check_failure=True, username="admin", password="admin"
|
||||
)
|
||||
|
||||
def execute_user_queries(queries, should_fail=False, failure_message="", check_failure=True):
|
||||
return execute_tester(tester_binary, queries, should_fail, failure_message, "user", "user", check_failure)
|
||||
|
||||
# Start the memgraph binary
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
time.sleep(0.1)
|
||||
assert memgraph.poll() is None, "Memgraph process died prematurely!"
|
||||
wait_for_server(7687)
|
||||
|
||||
# Register cleanup function
|
||||
@atexit.register
|
||||
def cleanup():
|
||||
if memgraph.poll() is None:
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
# Prepare all users
|
||||
execute_admin_queries(
|
||||
[
|
||||
"CREATE USER admin IDENTIFIED BY 'admin'",
|
||||
"GRANT ALL PRIVILEGES TO admin",
|
||||
"CREATE USER user IDENTIFIED BY 'user'",
|
||||
"GRANT ALL PRIVILEGES TO user",
|
||||
"GRANT LABELS :label1, :label2, :label3 TO user",
|
||||
"GRANT EDGE_TYPES :edgeType1, :edgeType2 TO user",
|
||||
"MERGE (l1:label1 {name: 'test1'})",
|
||||
"MERGE (l2:label2 {name: 'test2'})",
|
||||
"MATCH (l1:label1),(l2:label2) WHERE l1.name = 'test1' AND l2.name = 'test2' CREATE (l1)-[r:edgeType1]->(l2)",
|
||||
"MERGE (l3:label3 {name: 'test3'})",
|
||||
"MATCH (l1:label1),(l3:label3) WHERE l1.name = 'test1' AND l3.name = 'test3' CREATE (l1)-[r:edgeType2]->(l3)",
|
||||
"MERGE (mix:label3:label1 {name: 'test4'})",
|
||||
"MATCH (l1:label1),(mix:label3) WHERE l1.name = 'test1' AND mix.name = 'test4' CREATE (l1)-[r:edgeType2]->(mix)",
|
||||
]
|
||||
)
|
||||
|
||||
# Run the test with all combinations of permissions
|
||||
print("\033[1;36m~~ Starting edge filtering test ~~\033[0m")
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 3, "user", "user")
|
||||
execute_admin_queries(["DENY EDGE_TYPES :edgeType1 TO user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 2, "user", "user")
|
||||
execute_admin_queries(["GRANT EDGE_TYPES :edgeType1 TO user", "DENY LABELS :label3 TO user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 1, "user", "user")
|
||||
execute_admin_queries(["DENY LABELS :label1 TO user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 0, "user", "user")
|
||||
execute_admin_queries(["REVOKE LABELS * FROM user", "REVOKE EDGE_TYPES * FROM user"])
|
||||
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 0, "user", "user")
|
||||
|
||||
print("\033[1;36m~~ Finished edge filtering test ~~\033[0m\n")
|
||||
|
||||
# Shutdown the memgraph binary
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
|
||||
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "tester")
|
||||
filtering_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "filtering")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--memgraph", default=memgraph_binary)
|
||||
parser.add_argument("--tester", default=tester_binary)
|
||||
parser.add_argument("--filtering", default=filtering_binary)
|
||||
args = parser.parse_args()
|
||||
|
||||
execute_test(args.memgraph, args.tester, args.filtering)
|
||||
|
||||
sys.exit(0)
|
84
tests/integration/fine_grained_access/tester.cpp
Normal file
84
tests/integration/fine_grained_access/tester.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
//
|
||||
// Use of this software is governed by the Business Source License
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
// License, and you may not use this file except in compliance with the Business Source License.
|
||||
//
|
||||
// As of the Change Date specified in that file, in accordance with
|
||||
// the Business Source License, use of this software will be governed
|
||||
// by the Apache License, Version 2.0, included in the file
|
||||
// licenses/APL.txt.
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "communication/bolt/client.hpp"
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "io/network/utils.hpp"
|
||||
|
||||
DEFINE_string(address, "127.0.0.1", "Server address");
|
||||
DEFINE_int32(port, 7687, "Server port");
|
||||
DEFINE_string(username, "", "Username for the database");
|
||||
DEFINE_string(password, "", "Password for the database");
|
||||
DEFINE_bool(use_ssl, false, "Set to true to connect with SSL to the server.");
|
||||
|
||||
DEFINE_bool(check_failure, false, "Set to true to enable failure checking.");
|
||||
DEFINE_bool(should_fail, false, "Set to true to expect a failure.");
|
||||
DEFINE_string(failure_message, "", "Set to the expected failure message.");
|
||||
|
||||
/**
|
||||
* Executes queries passed as positional arguments and verifies whether they
|
||||
* succeeded, failed, failed with a specific error message or executed without a
|
||||
* specific error occurring.
|
||||
*/
|
||||
int main(int argc, char **argv) {
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
memgraph::communication::SSLInit sslInit;
|
||||
|
||||
memgraph::io::network::Endpoint endpoint(memgraph::io::network::ResolveHostname(FLAGS_address), FLAGS_port);
|
||||
|
||||
memgraph::communication::ClientContext context(FLAGS_use_ssl);
|
||||
memgraph::communication::bolt::Client client(&context);
|
||||
|
||||
client.Connect(endpoint, FLAGS_username, FLAGS_password);
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string query(argv[i]);
|
||||
try {
|
||||
client.Execute(query, {});
|
||||
} catch (const memgraph::communication::bolt::ClientQueryException &e) {
|
||||
if (!FLAGS_check_failure) {
|
||||
if (!FLAGS_failure_message.empty() && e.what() == FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have succeeded or failed with an error "
|
||||
"message that isn't equal to '{}' but it failed with that error "
|
||||
"message",
|
||||
FLAGS_failure_message);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (FLAGS_should_fail) {
|
||||
if (!FLAGS_failure_message.empty() && e.what() != FLAGS_failure_message) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed with an error message of '{}'' but "
|
||||
"instead it failed with '{}'",
|
||||
FLAGS_failure_message, e.what());
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
LOG_FATAL(
|
||||
"The query shoudn't have failed but it failed with an "
|
||||
"error message '{}'",
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
if (!FLAGS_check_failure) continue;
|
||||
if (FLAGS_should_fail) {
|
||||
LOG_FATAL(
|
||||
"The query should have failed but instead it executed "
|
||||
"successfully!");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
@ -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)
|
||||
|
@ -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
170
tests/unit/auth_checker.cpp
Normal 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));
|
||||
}
|
@ -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__)
|
||||
|
@ -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());
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user