Compare commits

...

24 Commits

Author SHA1 Message Date
josipmrden
8ce8098a71 Merge branch 'T0982-MG-Implement-EdgeType-filtering' into T0993-MG-lba-execution-triggers 2022-08-04 18:12:37 +02:00
josipmrden
f625f2c21a Fixed merge conflicts 2022-08-04 17:50:22 +02:00
josipmrden
b085532425 Merge branch 'E129-MG-label-based-authorization' into T0982-MG-Implement-EdgeType-filtering 2022-08-04 17:28:09 +02:00
niko4299
f385e080ab Added integration test for lba 2022-08-03 14:04:36 +02:00
niko4299
633214baa8 Tested FineGrainedAccessHandler, need to test AuthChecker 2022-08-02 16:30:41 +02:00
niko4299
fc10f5676e Fix 2022-08-02 15:38:06 +02:00
niko4299
987cfe2c6d Removed Expand Path 2022-08-02 14:52:21 +02:00
josipmrden
67ea684d1d Added trigger support with LBA 2022-08-02 13:36:53 +02:00
niko4299
e5a04489b1 Removed FineGrainedAccessChecker 2022-08-02 13:00:09 +02:00
Boris Taševski
480df4ed69 Merge old Label Based Auth Epic branch into new one because of commits with bad checks on the old epic branch (#478)
* grammar expanded; (#462)

* 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

* Removed extra lambda

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

* Added FineGrainedAccessChecker to Context

* fixed failing tests for label based authorization (#480)

* Marked FineGrainedAccessChecker ctor explicit; Introduced change to clang-tidy; (#483)

Co-authored-by: niko4299 <51059248+niko4299@users.noreply.github.com>
2022-08-02 12:51:22 +02:00
niko4299
65ef870e17 EdgeTypeFiltering working, just need to test everything again 2022-08-01 16:33:18 +02:00
niko4299
2e0ae9153f MATCH filtering working 2022-08-01 14:06:47 +02:00
niko4299
3843366e7b Removed storage changes 2022-08-01 11:23:43 +02:00
niko4299
c830bc7d81 Added edge filtering to storage, need to add filtering in simple Expand in operator.cpp 2022-07-22 16:47:04 +02:00
niko4299
ca6ee0c209 testing 2022-07-22 12:01:37 +02:00
niko4299
17cb59d75a Added filtering 2022-07-22 11:19:56 +02:00
niko4299
5e87ce9f65 changed 2022-07-22 09:36:17 +02:00
niko4299
a2643cc133 [E129<-T0955-MG] Expand ExecutionContext with label related information (#467)
* added

* Added FineGrainedAccessChecker to Context

* fixed
2022-07-21 20:23:55 +02:00
niko4299
f85ee31b4b [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
2022-07-21 15:09:28 +02:00
Boris Taševski
5914b97f5e 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
2022-07-21 14:00:56 +02:00
niko4299
019f226b5e current 2022-07-21 12:33:50 +02:00
Boris Taševski
92b4e39b21 grammar expanded; (#462) 2022-07-21 12:29:17 +02:00
niko4299
5f8ae644ff Added AccessChecker to ExecutionContext 2022-07-21 10:30:16 +02:00
niko4299
761f536b75 GRANT, REVOKE, DENY and access_checker DONE 2022-07-20 14:22:26 +02:00
27 changed files with 942 additions and 88 deletions

View File

@@ -130,7 +130,7 @@ jobs:
source /opt/toolchain-v4/activate
# Restrict clang-tidy results only to the modified parts
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt
git diff -U0 ${{ env.BASE_BRANCH }}... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build -regex ".+\.cpp" | tee ./build/clang_tidy_output.txt
# Fail if any warning is reported
! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null

View File

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

View File

@@ -8,7 +8,10 @@
#include "auth/models.hpp"
#include <algorithm>
#include <iterator>
#include <regex>
#include <unordered_set>
#include <gflags/gflags.h>
@@ -98,12 +101,7 @@ std::string PermissionLevelToString(PermissionLevel level) {
}
}
Permissions::Permissions(uint64_t grants, uint64_t denies) {
// The deny bitmask has higher priority than the grant bitmask.
denies_ = denies;
// Mask out the grant bitmask to make sure that it is correct.
grants_ = grants & (~denies);
}
Permissions::Permissions(uint64_t grants, uint64_t denies) : grants_(grants & (~denies)), denies_(denies) {}
PermissionLevel Permissions::Has(Permission permission) const {
// Check for the deny first because it has greater priority than a grant.
@@ -183,19 +181,176 @@ bool operator==(const Permissions &first, const Permissions &second) {
bool operator!=(const Permissions &first, const Permissions &second) { return !(first == second); }
const std::string ASTERISK = "*";
FineGrainedAccessPermissions::FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants,
const std::unordered_set<std::string> &denies)
: grants_(grants), denies_(denies) {}
PermissionLevel FineGrainedAccessPermissions::Has(const std::string &permission) const {
if ((denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) || denies_.find(permission) != denies_.end()) {
return PermissionLevel::DENY;
}
if ((grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) || grants_.find(permission) != denies_.end()) {
return PermissionLevel::GRANT;
}
return PermissionLevel::NEUTRAL;
}
void FineGrainedAccessPermissions::Grant(const std::string &permission) {
if (permission == ASTERISK) {
grants_.clear();
grants_.insert(permission);
return;
}
auto deniedPermissionIter = denies_.find(permission);
if (deniedPermissionIter != denies_.end()) {
denies_.erase(deniedPermissionIter);
}
if (grants_.size() == 1 && grants_.find(ASTERISK) != grants_.end()) {
grants_.erase(ASTERISK);
}
if (grants_.find(permission) == grants_.end()) {
grants_.insert(permission);
}
}
void FineGrainedAccessPermissions::Revoke(const std::string &permission) {
if (permission == ASTERISK) {
grants_.clear();
denies_.clear();
return;
}
auto deniedPermissionIter = denies_.find(permission);
auto grantedPermissionIter = grants_.find(permission);
if (deniedPermissionIter != denies_.end()) {
denies_.erase(deniedPermissionIter);
}
if (grantedPermissionIter != grants_.end()) {
grants_.erase(grantedPermissionIter);
}
}
void FineGrainedAccessPermissions::Deny(const std::string &permission) {
if (permission == ASTERISK) {
denies_.clear();
denies_.insert(permission);
return;
}
auto grantedPermissionIter = grants_.find(permission);
if (grantedPermissionIter != grants_.end()) {
grants_.erase(grantedPermissionIter);
}
if (denies_.size() == 1 && denies_.find(ASTERISK) != denies_.end()) {
denies_.erase(ASTERISK);
}
if (denies_.find(permission) == denies_.end()) {
denies_.insert(permission);
}
}
nlohmann::json FineGrainedAccessPermissions::Serialize() const {
nlohmann::json data = nlohmann::json::object();
data["grants"] = grants_;
data["denies"] = denies_;
return data;
}
FineGrainedAccessPermissions FineGrainedAccessPermissions::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load permissions data!");
}
return FineGrainedAccessPermissions(data["grants"], data["denies"]);
}
const std::unordered_set<std::string> &FineGrainedAccessPermissions::grants() const { return grants_; }
const std::unordered_set<std::string> &FineGrainedAccessPermissions::denies() const { return denies_; }
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return first.grants() == second.grants() && first.denies() == second.denies();
}
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second) {
return !(first == second);
}
FineGrainedAccessHandler::FineGrainedAccessHandler(const FineGrainedAccessPermissions &labelPermissions,
const FineGrainedAccessPermissions &edgeTypePermissions)
: label_permissions_(labelPermissions), edge_type_permissions_(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(label_permissions, 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)
: rolename_(utils::ToLowerCase(rolename)), permissions_(permissions) {}
Role::Role(const std::string &rolename, const Permissions &permissions,
const FineGrainedAccessHandler &fine_grained_access_handler)
: rolename_(utils::ToLowerCase(rolename)),
permissions_(permissions),
fine_grained_access_handler_(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 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_handler"] = fine_grained_access_handler_.Serialize();
return data;
}
@@ -203,21 +358,28 @@ Role Role::Deserialize(const nlohmann::json &data) {
if (!data.is_object()) {
throw AuthException("Couldn't load role data!");
}
if (!data["rolename"].is_string() || !data["permissions"].is_object()) {
if (!data["rolename"].is_string() || !data["permissions"].is_object() ||
!data["fine_grained_access_handler"].is_object()) {
throw AuthException("Couldn't load role data!");
}
auto permissions = Permissions::Deserialize(data["permissions"]);
return {data["rolename"], permissions};
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["rolename"], permissions, 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(const std::string &username) : username_(utils::ToLowerCase(username)) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions)
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
User::User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
const FineGrainedAccessHandler &fine_grained_access_handler)
: username_(utils::ToLowerCase(username)),
password_hash_(password_hash),
permissions_(permissions),
fine_grained_access_handler_(fine_grained_access_handler) {}
bool User::CheckPassword(const std::string &password) {
if (password_hash_.empty()) return true;
@@ -260,16 +422,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_.grants() | role_->permissions().grants(),
permissions_.denies() | role_->permissions().denies()};
}
return permissions_;
}
FineGrainedAccessPermissions User::GetFineGrainedAccessLabelPermissions() const {
if (role_) {
std::unordered_set<std::string> resultGrants;
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_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_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 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()) {
@@ -283,6 +493,7 @@ nlohmann::json User::Serialize() const {
data["username"] = username_;
data["password_hash"] = password_hash_;
data["permissions"] = 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;
}
@@ -291,15 +502,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"]);
return {data["username"], data["password_hash"], permissions};
auto fine_grained_access_handler = FineGrainedAccessHandler::Deserialize(data["fine_grained_access_handler"]);
return {data["username"], data["password_hash"], permissions, fine_grained_access_handler};
}
bool operator==(const User &first, const User &second) {
return first.username_ == second.username_ && first.password_hash_ == second.password_hash_ &&
first.permissions_ == second.permissions_ && first.role_ == second.role_;
first.permissions_ == second.permissions_ && first.role_ == second.role_ &&
first.fine_grained_access_handler_ == second.fine_grained_access_handler_;
}
} // namespace memgraph::auth

View File

@@ -10,8 +10,10 @@
#include <optional>
#include <string>
#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
@@ -88,15 +90,74 @@ bool operator==(const Permissions &first, const Permissions &second);
bool operator!=(const Permissions &first, const Permissions &second);
class FineGrainedAccessPermissions final {
public:
explicit FineGrainedAccessPermissions(const std::unordered_set<std::string> &grants = {},
const std::unordered_set<std::string> &denies = {});
PermissionLevel Has(const std::string &permission) const;
void Grant(const std::string &permission);
void Revoke(const std::string &permission);
void Deny(const std::string &permission);
nlohmann::json Serialize() const;
/// @throw AuthException if unable to deserialize.
static FineGrainedAccessPermissions Deserialize(const nlohmann::json &data);
const std::unordered_set<std::string> &grants() const;
const std::unordered_set<std::string> &denies() const;
private:
std::unordered_set<std::string> grants_{};
std::unordered_set<std::string> denies_{};
};
bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAccessPermissions &second);
class FineGrainedAccessHandler final {
public:
explicit FineGrainedAccessHandler(
const FineGrainedAccessPermissions &labelPermissions = FineGrainedAccessPermissions(),
const FineGrainedAccessPermissions &edgeTypePermissions = FineGrainedAccessPermissions());
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);
Role(const std::string &rolename, const Permissions &permissions);
Role(const std::string &rolename, const Permissions &permissions,
const FineGrainedAccessHandler &fine_grained_access_handler);
const std::string &rolename() const;
const Permissions &permissions() const;
Permissions &permissions();
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
nlohmann::json Serialize() const;
@@ -108,6 +169,7 @@ class Role final {
private:
std::string rolename_;
Permissions permissions_;
FineGrainedAccessHandler fine_grained_access_handler_;
};
bool operator==(const Role &first, const Role &second);
@@ -117,7 +179,8 @@ class User final {
public:
User(const std::string &username);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions);
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
const FineGrainedAccessHandler &fine_grained_access_handler);
/// @throw AuthException if unable to verify the password.
bool CheckPassword(const std::string &password);
@@ -130,11 +193,15 @@ class User final {
void ClearRole();
Permissions GetPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessLabelPermissions() const;
FineGrainedAccessPermissions GetFineGrainedAccessEdgeTypePermissions() const;
const std::string &username() const;
const Permissions &permissions() const;
Permissions &permissions();
const FineGrainedAccessHandler &fine_grained_access_handler() const;
FineGrainedAccessHandler &fine_grained_access_handler();
const Role *role() const;
@@ -149,6 +216,7 @@ class User final {
std::string username_;
std::string password_hash_;
Permissions permissions_;
FineGrainedAccessHandler fine_grained_access_handler_;
std::optional<Role> role_;
};

View File

@@ -506,7 +506,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,9 +751,28 @@ 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) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
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?
@@ -762,8 +781,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
}
void DenyPrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
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?
@@ -772,8 +792,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
}
void RevokePrivilege(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges) override {
EditPermissions(user_or_role, privileges, [](auto *permissions, const auto &permission) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
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?
@@ -784,7 +805,9 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
private:
template <class TEditFun>
void EditPermissions(const std::string &user_or_role,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges, const TEditFun &edit_fun) {
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges,
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.");
}
@@ -804,11 +827,25 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
for (const auto &permission : permissions) {
edit_fun(&user->permissions(), permission);
}
for (const auto &label : labels) {
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_handler().label_permissions(), label);
}
for (const auto &edgeType : edgeTypes) {
edit_fun(&role->fine_grained_access_handler().edge_type_permissions(), edgeType);
}
locked_auth->SaveRole(*role);
}
} catch (const memgraph::auth::AuthException &e) {
@@ -848,6 +885,20 @@ class AuthChecker final : public memgraph::query::AuthChecker {
return maybe_user.has_value() && IsUserAuthorized(*maybe_user, privileges);
}
bool IsUserAuthorizedLabels(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const std::vector<memgraph::storage::LabelId> &labels) const final {
return std::any_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) const final {
return user->GetFineGrainedAccessEdgeTypePermissions().Has(dba->EdgeTypeToName(edgeType)) ==
memgraph::auth::PermissionLevel::GRANT;
}
private:
memgraph::utils::Synchronized<memgraph::auth::Auth, memgraph::utils::WritePrioritizedRWLock> *auth_;
};

View File

@@ -11,13 +11,19 @@
#pragma once
#include "auth/models.hpp"
#include "query/frontend/ast/ast.hpp"
namespace memgraph::query {
class AuthChecker {
public:
virtual bool IsUserAuthorized(const std::optional<std::string> &username,
const std::vector<query::AuthQuery::Privilege> &privileges) const = 0;
virtual bool IsUserAuthorizedLabels(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const std::vector<memgraph::storage::LabelId> &labels) const = 0;
virtual bool IsUserAuthorizedEdgeType(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const memgraph::storage::EdgeTypeId &edgeType) const = 0;
};
class AllowEverythingAuthChecker final : public query::AuthChecker {
@@ -25,5 +31,14 @@ class AllowEverythingAuthChecker final : public query::AuthChecker {
const std::vector<query::AuthQuery::Privilege> &privileges) const override {
return true;
}
bool IsUserAuthorizedLabels(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const std::vector<memgraph::storage::LabelId> &labels) const override {
return true;
};
bool IsUserAuthorizedEdgeType(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const memgraph::storage::EdgeTypeId &edgeType) const override {
return true;
};
};
} // namespace memgraph::query
} // namespace memgraph::query

View File

@@ -72,6 +72,8 @@ struct ExecutionContext {
ExecutionStats execution_stats;
TriggerContextCollector *trigger_context_collector{nullptr};
utils::AsyncTimer timer;
AuthChecker *auth_checker{nullptr};
memgraph::auth::User *user{nullptr};
};
static_assert(std::is_move_assignable_v<ExecutionContext>, "ExecutionContext must be move assignable!");

View File

@@ -2234,6 +2234,7 @@ cpp<#
(:serialize (:slk))
(:clone))
(lcp:define-class auth-query (query)
((action "Action" :scope :public)
(user "std::string" :scope :public)
@@ -2242,7 +2243,9 @@ cpp<#
(password "Expression *" :initval "nullptr" :scope :public
:slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression"))
(privileges "std::vector<Privilege>" :scope :public))
(privileges "std::vector<Privilege>" :scope :public)
(labels "std::vector<std::string>" :scope :public)
(edgeTypes "std::vector<std::string>" :scope :public))
(:public
(lcp:define-enum action
(create-role drop-role show-roles create-user set-password drop-user
@@ -2264,13 +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<Privilege> privileges, std::vector<std::string> labels,
std::vector<std::string> edgeTypes)
: action_(action),
user_(user),
role_(role),
user_or_role_(user_or_role),
password_(password),
privileges_(privileges) {}
privileges_(privileges),
labels_(labels),
edgetypes_(edgeTypes) {}
cpp<#)
(:private
#>cpp

View File

@@ -1277,7 +1277,13 @@ 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()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
if (privilege->EDGE_TYPES()) {
auth->edgetypes_ = 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)));
}
}
} else {
/* grant all privileges */
@@ -1295,7 +1301,13 @@ 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()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
if (privilege->EDGE_TYPES()) {
auth->edgetypes_ = 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)));
}
}
} else {
/* deny all privileges */
@@ -1313,7 +1325,13 @@ 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()) {
auth->privileges_.push_back(std::any_cast<AuthQuery::Privilege>(privilege->accept(this)));
if (privilege->EDGE_TYPES()) {
auth->edgetypes_ = 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)));
}
}
} else {
/* revoke all privileges */
@@ -1322,6 +1340,35 @@ antlrcpp::Any CypherMainVisitor::visitRevokePrivilege(MemgraphCypher::RevokePriv
return auth;
}
antlrcpp::Any CypherMainVisitor::visitLabelList(MemgraphCypher::LabelListContext *ctx) {
std::vector<std::string> labels;
if (ctx->listOfLabels()) {
for (auto *label : ctx->listOfLabels()->label()) {
labels.push_back(std::any_cast<std::string>(label->symbolicName()->accept(this)));
}
} else {
labels.emplace_back("*");
}
return labels;
}
/**
* @return AuthQuery*
*/
antlrcpp::Any CypherMainVisitor::visitEdgeTypeList(MemgraphCypher::EdgeTypeListContext *ctx) {
std::vector<std::string> edgeTypes;
if (ctx->listOfEdgeTypes()) {
for (auto *edgeType : ctx->listOfEdgeTypes()->edgeType()) {
edgeTypes.push_back(std::any_cast<std::string>(edgeType->symbolicName()->accept(this)));
}
} else {
edgeTypes.emplace_back("*");
}
return edgeTypes;
}
/**
* @return AuthQuery::Privilege
*/

View File

@@ -468,6 +468,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
*/
@@ -478,6 +483,11 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitShowPrivileges(MemgraphCypher::ShowPrivilegesContext *ctx) override;
/**
* @return AuthQuery::LabelList
*/
antlrcpp::Any visitLabelList(MemgraphCypher::LabelListContext *ctx) override;
/**
* @return AuthQuery*
*/

View File

@@ -45,6 +45,7 @@ memgraphCypherKeyword : cypherKeyword
| DENY
| DROP
| DUMP
| EDGE_TYPES
| EXECUTE
| FOR
| FOREACH
@@ -56,6 +57,7 @@ memgraphCypherKeyword : cypherKeyword
| IDENTIFIED
| ISOLATION
| KAFKA
| LABELS
| LEVEL
| LOAD
| LOCK
@@ -254,10 +256,24 @@ 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 )* ;
label : COLON symbolicName ;
showPrivileges : SHOW PRIVILEGES FOR userOrRole=userOrRoleName ;
showRoleForUser : SHOW ROLE FOR user=userOrRoleName ;

View File

@@ -66,6 +66,7 @@ IDENTIFIED : I D E N T I F I E D ;
IGNORE : I G N O R E ;
ISOLATION : I S O L A T I O N ;
KAFKA : K A F K A ;
LABELS : L A B E L S ;
LEVEL : L E V E L ;
LOAD : L O A D ;
LOCK : L O C K ;
@@ -114,3 +115,4 @@ USER : U S E R ;
USERS : U S E R S ;
VERSION : V E R S I O N ;
WEBSOCKET : W E B S O C K E T ;
EDGE_TYPES : E D G E UNDERSCORE T Y P E S ;

View File

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

View File

@@ -43,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"
@@ -273,6 +274,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);
@@ -280,6 +282,8 @@ 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->edgetypes_;
std::vector<std::string> labels = auth_query->labels_;
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
Callback callback;
@@ -292,10 +296,11 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
AuthQuery::Action::REVOKE_PRIVILEGE, AuthQuery::Action::SHOW_PRIVILEGES, AuthQuery::Action::SHOW_USERS_FOR_ROLE,
AuthQuery::Action::SHOW_ROLE_FOR_USER};
if (license_check_result.HasError() && enterprise_only_methods.contains(auth_query->action_)) {
throw utils::BasicException(
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "advanced authentication features"));
}
// if (license_check_result.HasError() && enterprise_only_methods.contains(auth_query->action_)) {
// throw utils::BasicException(
// utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "advanced authentication
// features"));
// }
switch (auth_query->action_) {
case AuthQuery::Action::CREATE_USER:
@@ -309,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>>();
@@ -384,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] {
auth->GrantPrivilege(user_or_role, privileges);
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] {
auth->DenyPrivilege(user_or_role, privileges);
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] {
auth->RevokePrivilege(user_or_role, privileges);
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;
@@ -897,7 +902,7 @@ struct PullPlanVector {
struct PullPlan {
explicit PullPlan(std::shared_ptr<CachedPlan> plan, const Parameters &parameters, bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
TriggerContextCollector *trigger_context_collector = nullptr,
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector = nullptr,
std::optional<size_t> memory_limit = {});
std::optional<plan::ProfilingStatsWithTotalTime> Pull(AnyStream *stream, std::optional<int> n,
const std::vector<Symbol> &output_symbols,
@@ -926,7 +931,8 @@ struct PullPlan {
PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &parameters, const bool is_profile_query,
DbAccessor *dba, InterpreterContext *interpreter_context, utils::MemoryResource *execution_memory,
TriggerContextCollector *trigger_context_collector, const std::optional<size_t> memory_limit)
std::optional<std::string> username, TriggerContextCollector *trigger_context_collector,
const std::optional<size_t> memory_limit)
: plan_(plan),
cursor_(plan->plan().MakeCursor(execution_memory)),
frame_(plan->symbol_table().max_position(), execution_memory),
@@ -937,6 +943,12 @@ PullPlan::PullPlan(const std::shared_ptr<CachedPlan> plan, const Parameters &par
ctx_.evaluation_context.parameters = parameters;
ctx_.evaluation_context.properties = NamesToProperties(plan->ast_storage().properties_, dba);
ctx_.evaluation_context.labels = NamesToLabels(plan->ast_storage().labels_, dba);
#ifdef MG_ENTERPRISE
if (username.has_value()) {
ctx_.user = interpreter_context->auth->GetUser(*username);
ctx_.auth_checker = interpreter_context->auth_checker;
}
#endif
if (interpreter_context->config.execution_timeout_sec > 0) {
ctx_.timer = utils::AsyncTimer{interpreter_context->config.execution_timeout_sec};
}
@@ -1110,6 +1122,7 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper)
PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string, TypedValue> *summary,
InterpreterContext *interpreter_context, DbAccessor *dba,
utils::MemoryResource *execution_memory, std::vector<Notification> *notifications,
const std::string *username,
TriggerContextCollector *trigger_context_collector = nullptr) {
auto *cypher_query = utils::Downcast<CypherQuery>(parsed_query.query);
@@ -1118,6 +1131,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) {
@@ -1153,8 +1167,9 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
header.push_back(
utils::FindOr(parsed_query.stripped_query.named_expressions(), symbol.token_position(), symbol.name()).first);
}
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, trigger_context_collector, memory_limit);
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory,
StringPointerToOptional(username), trigger_context_collector, memory_limit);
return PreparedQuery{std::move(header), std::move(parsed_query.required_privileges),
[pull_plan = std::move(pull_plan), output_symbols = std::move(output_symbols), summary](
AnyStream *stream, std::optional<int> n) -> std::optional<QueryHandlerResult> {
@@ -1214,7 +1229,8 @@ PreparedQuery PrepareExplainQuery(ParsedQuery parsed_query, std::map<std::string
PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
DbAccessor *dba, utils::MemoryResource *execution_memory) {
DbAccessor *dba, utils::MemoryResource *execution_memory,
const std::string *username) {
const std::string kProfileQueryStart = "profile ";
MG_ASSERT(utils::StartsWith(utils::ToLowerCase(parsed_query.stripped_query.query()), kProfileQueryStart),
@@ -1264,12 +1280,14 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
parsed_inner_query.stripped_query.hash(), std::move(parsed_inner_query.ast_storage), cypher_query,
parsed_inner_query.parameters, parsed_inner_query.is_cacheable ? &interpreter_context->plan_cache : nullptr, dba);
auto rw_type_checker = plan::ReadWriteTypeChecker();
auto optional_username = StringPointerToOptional(username);
rw_type_checker.InferRWType(const_cast<plan::LogicalOperator &>(cypher_query_plan->plan()));
return PreparedQuery{{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"},
std::move(parsed_query.required_privileges),
[plan = std::move(cypher_query_plan), parameters = std::move(parsed_inner_query.parameters),
summary, dba, interpreter_context, execution_memory, memory_limit,
summary, dba, interpreter_context, execution_memory, memory_limit, optional_username,
// We want to execute the query we are profiling lazily, so we delay
// the construction of the corresponding context.
stats_and_total_time = std::optional<plan::ProfilingStatsWithTotalTime>{},
@@ -1278,7 +1296,7 @@ PreparedQuery PrepareProfileQuery(ParsedQuery parsed_query, bool in_explicit_tra
// No output symbols are given so that nothing is streamed.
if (!stats_and_total_time) {
stats_and_total_time = PullPlan(plan, parameters, true, dba, interpreter_context,
execution_memory, nullptr, memory_limit)
execution_memory, optional_username, nullptr, memory_limit)
.Pull(stream, {}, {}, summary);
pull_plan = std::make_shared<PullPlanVector>(ProfilingStatsToTable(*stats_and_total_time));
}
@@ -1413,7 +1431,7 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans
PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
DbAccessor *dba, utils::MemoryResource *execution_memory) {
DbAccessor *dba, utils::MemoryResource *execution_memory, const std::string *username) {
if (in_explicit_transaction) {
throw UserModificationInMulticommandTxException();
}
@@ -1433,8 +1451,8 @@ PreparedQuery PrepareAuthQuery(ParsedQuery parsed_query, bool in_explicit_transa
[fn = callback.fn](Frame *, ExecutionContext *) { return fn(); }),
0.0, AstStorage{}, symbol_table));
auto pull_plan =
std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context, execution_memory);
auto pull_plan = std::make_shared<PullPlan>(plan, parsed_query.parameters, false, dba, interpreter_context,
execution_memory, StringPointerToOptional(username));
return PreparedQuery{
callback.header, std::move(parsed_query.required_privileges),
[pull_plan = std::move(pull_plan), callback = std::move(callback), output_symbols = std::move(output_symbols),
@@ -2146,7 +2164,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
if (utils::Downcast<CypherQuery>(parsed_query.query)) {
prepared_query = PrepareCypherQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
&*execution_db_accessor_, &query_execution->execution_memory,
&query_execution->notifications,
&query_execution->notifications, username,
trigger_context_collector_ ? &*trigger_context_collector_ : nullptr);
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
@@ -2154,7 +2172,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, &*execution_db_accessor_,
&query_execution->execution_memory_with_exception);
&query_execution->execution_memory_with_exception, username);
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
&query_execution->execution_memory);
@@ -2164,7 +2182,7 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, &*execution_db_accessor_,
&query_execution->execution_memory_with_exception);
&query_execution->execution_memory_with_exception, username);
} else if (utils::Downcast<InfoQuery>(parsed_query.query)) {
prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
interpreter_context_, interpreter_context_->db,
@@ -2249,8 +2267,14 @@ void RunTriggersIndividually(const utils::SkipList<Trigger> &triggers, Interpret
trigger_context.AdaptForAccessor(&db_accessor);
try {
auto owner = trigger.Owner();
memgraph::auth::User *user = nullptr;
if (owner.has_value()) {
user = interpreter_context->auth->GetUser(*owner);
}
trigger.Execute(&db_accessor, &execution_memory, interpreter_context->config.execution_timeout_sec,
&interpreter_context->is_shutting_down, trigger_context, interpreter_context->auth_checker);
&interpreter_context->is_shutting_down, trigger_context, user, interpreter_context->auth_checker);
} catch (const utils::BasicException &exception) {
spdlog::warn("Trigger '{}' failed with exception:\n{}", trigger.Name(), exception.what());
db_accessor.Abort();
@@ -2304,8 +2328,15 @@ void Interpreter::Commit() {
utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize};
AdvanceCommand();
try {
auto owner = trigger.Owner();
memgraph::auth::User *user = nullptr;
if (owner.has_value()) {
user = interpreter_context_->auth->GetUser(*owner);
}
trigger.Execute(&*execution_db_accessor_, &execution_memory, interpreter_context_->config.execution_timeout_sec,
&interpreter_context_->is_shutting_down, *trigger_context, interpreter_context_->auth_checker);
&interpreter_context_->is_shutting_down, *trigger_context, user,
interpreter_context_->auth_checker);
} catch (const utils::BasicException &e) {
throw utils::BasicException(
fmt::format("Trigger '{}' caused the transaction to fail.\nException: {}", trigger.Name(), e.what()));

View File

@@ -13,6 +13,7 @@
#include <gflags/gflags.h>
#include "auth/models.hpp"
#include "query/auth_checker.hpp"
#include "query/config.hpp"
#include "query/context.hpp"
@@ -99,14 +100,19 @@ class AuthQueryHandler {
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
virtual memgraph::auth::User *GetUser(const std::string &username) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges) = 0;
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
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) = 0;
virtual void DenyPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
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, const std::vector<std::string> &edgeTypes) = 0;
};
enum class QueryHandlerResult { COMMIT, ABORT, NOTHING };

View File

@@ -38,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"
@@ -683,6 +684,13 @@ 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->IsUserAuthorizedEdgeType(context.user, context.db_accessor, edge.EdgeType()) ||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
edge.To().Labels(storage::View::OLD).GetValue()) ||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
edge.From().Labels(storage::View::OLD).GetValue())))
continue;
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::IN);
return true;
@@ -695,6 +703,13 @@ 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->IsUserAuthorizedEdgeType(context.user, context.db_accessor, edge.EdgeType()) ||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
edge.To().Labels(storage::View::OLD).GetValue()) ||
!context.auth_checker->IsUserAuthorizedLabels(context.user, context.db_accessor,
edge.From().Labels(storage::View::OLD).GetValue())))
continue;
frame[self_.common_.edge_symbol] = edge;
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
@@ -832,6 +847,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()) {
@@ -1012,8 +1028,6 @@ class ExpandVariableCursor : public Cursor {
edges_on_frame.resize(std::min(edges_on_frame.size(), edges_.size()));
}
// if we are here, we have a valid stack,
// get the edge, increase the relevant iterator
auto current_edge = *edges_it_.back()++;
// Check edge-uniqueness.
@@ -1021,11 +1035,11 @@ class ExpandVariableCursor : public Cursor {
std::any_of(edges_on_frame.begin(), edges_on_frame.end(),
[&current_edge](const TypedValue &edge) { return current_edge.first == edge.ValueEdge(); });
if (found_existing) continue;
AppendEdge(current_edge.first, &edges_on_frame);
VertexAccessor current_vertex =
current_edge.second == EdgeAtom::Direction::IN ? current_edge.first.From() : current_edge.first.To();
AppendEdge(current_edge.first, &edges_on_frame);
if (!self_.common_.existing_node) {
frame[self_.common_.node_symbol] = current_vertex;
}
@@ -1362,6 +1376,7 @@ class SingleSourceShortestPathCursor : public query::plan::Cursor {
const auto &vertex = vertex_value.ValueVertex();
processed_.emplace(vertex, std::nullopt);
expand_from_vertex(vertex);
// go back to loop start and see if we expanded anything

View File

@@ -195,7 +195,7 @@ std::shared_ptr<Trigger::TriggerPlan> Trigger::GetPlan(DbAccessor *db_accessor,
void Trigger::Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution_memory,
const double max_execution_time_sec, std::atomic<bool> *is_shutting_down,
const TriggerContext &context, const AuthChecker *auth_checker) const {
const TriggerContext &context, memgraph::auth::User *user, AuthChecker *auth_checker) const {
if (!context.ShouldEventTrigger(event_type_)) {
return;
}
@@ -215,6 +215,8 @@ void Trigger::Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution
ctx.timer = utils::AsyncTimer(max_execution_time_sec);
ctx.is_shutting_down = is_shutting_down;
ctx.is_profile_query = false;
ctx.user = user;
ctx.auth_checker = auth_checker;
// Set up temporary memory for a single Pull. Initial memory comes from the
// stack. 256 KiB should fit on the stack and should be more than enough for a

View File

@@ -39,8 +39,8 @@ struct Trigger {
const query::AuthChecker *auth_checker);
void Execute(DbAccessor *dba, utils::MonotonicBufferResource *execution_memory, double max_execution_time_sec,
std::atomic<bool> *is_shutting_down, const TriggerContext &context,
const AuthChecker *auth_checker) const;
std::atomic<bool> *is_shutting_down, const TriggerContext &context, memgraph::auth::User *user,
AuthChecker *auth_checker) const;
bool operator==(const Trigger &other) const { return name_ == other.name_; }
// NOLINTNEXTLINE (modernize-use-nullptr)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,129 @@
#!/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 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)",
]
)
# 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"], 2, "user", "user")
execute_admin_queries(["DENY EDGE_TYPES :edgeType1 TO user"])
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 1, "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(["REVOKE LABELS * FROM user", "REVOKE EDGE_TYPES * FROM user"])
execute_filtering(filtering_binary, ["MATCH (n)-[r]->(m) RETURN n,r,m"], 0, "user", "user")
print("\033[1;36m~~ Finished edge filtering test ~~\033[0m\n")
# Shutdown the memgraph binary
memgraph.terminate()
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
if __name__ == "__main__":
memgraph_binary = os.path.join(PROJECT_DIR, "build", "memgraph")
tester_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "tester")
filtering_binary = os.path.join(PROJECT_DIR, "build", "tests", "integration", "lba", "filtering")
parser = argparse.ArgumentParser()
parser.add_argument("--memgraph", default=memgraph_binary)
parser.add_argument("--tester", default=tester_binary)
parser.add_argument("--filtering", default=filtering_binary)
args = parser.parse_args()
execute_test(args.memgraph, args.tester, args.filtering)
sys.exit(0)

View File

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

View File

@@ -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,8 +536,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto user1 = User("test", "pw", perms);
auto user2 = User("Test", "pw", perms);
auto fine_grained_access_handler = FineGrainedAccessHandler();
auto user1 = User("test", "pw", perms, fine_grained_access_handler);
auto user2 = User("Test", "pw", perms, fine_grained_access_handler);
ASSERT_EQ(user1, user2);
ASSERT_EQ(user1.username(), user2.username());
ASSERT_EQ(user1.username(), "test");
@@ -485,8 +554,9 @@ TEST(AuthWithoutStorage, CaseInsensitivity) {
}
{
auto perms = Permissions();
auto role1 = Role("role", perms);
auto role2 = Role("Role", perms);
auto fine_grained_access_handler = FineGrainedAccessHandler();
auto role1 = Role("role", perms, fine_grained_access_handler);
auto role2 = Role("Role", perms, fine_grained_access_handler);
ASSERT_EQ(role1, role2);
ASSERT_EQ(role1.rolename(), role2.rolename());
ASSERT_EQ(role1.rolename(), "role");

View File

@@ -531,9 +531,9 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
memgraph::query::test_common::OnCreate { \
std::vector<memgraph::query::Clause *> { __VA_ARGS__ } \
}
#define CREATE_INDEX_ON(label, property) \
#define CREATE_INDEX_ON(label, property) \
storage.Create<memgraph::query::IndexQuery>(memgraph::query::IndexQuery::Action::CREATE, (label), \
std::vector<memgraph::query::PropertyIx>{(property)})
std::vector<memgraph::query::PropertyIx>{(property)})
#define QUERY(...) memgraph::query::test_common::GetQuery(storage, __VA_ARGS__)
#define SINGLE_QUERY(...) memgraph::query::test_common::GetSingleQuery(storage.Create<SingleQuery>(), __VA_ARGS__)
#define UNION(...) memgraph::query::test_common::GetCypherUnion(storage.Create<CypherUnion>(true), __VA_ARGS__)
@@ -583,7 +583,8 @@ auto GetForeach(AstStorage &storage, NamedExpression *named_expr, const std::vec
#define COALESCE(...) storage.Create<memgraph::query::Coalesce>(std::vector<memgraph::query::Expression *>{__VA_ARGS__})
#define EXTRACT(variable, list, expr) \
storage.Create<memgraph::query::Extract>(storage.Create<memgraph::query::Identifier>(variable), list, expr)
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges))
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges, labels, edgeTypes) \
storage.Create<memgraph::query::AuthQuery>((action), (user), (role), (user_or_role), password, (privileges), \
(labels), (edgeTypes))
#define DROP_USER(usernames) storage.Create<memgraph::query::DropUser>((usernames))
#define CALL_PROCEDURE(...) memgraph::query::test_common::GetCallProcedure(storage, __VA_ARGS__)

View File

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

View File

@@ -21,6 +21,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 +38,12 @@ class MockAuthChecker : public memgraph::query::AuthChecker {
public:
MOCK_CONST_METHOD2(IsUserAuthorized, bool(const std::optional<std::string> &username,
const std::vector<memgraph::query::AuthQuery::Privilege> &privileges));
MOCK_CONST_METHOD3(IsUserAuthorizedEdgeType,
bool(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const memgraph::storage::EdgeTypeId &edgeType));
MOCK_CONST_METHOD3(IsUserAuthorizedLabels,
bool(const memgraph::auth::User *user, const memgraph::query::DbAccessor *dba,
const std::vector<memgraph::storage::LabelId> &labels));
};
} // namespace