Compare commits
23 Commits
master
...
T0993-MG-l
Author | SHA1 | Date | |
---|---|---|---|
|
8ce8098a71 | ||
|
f625f2c21a | ||
|
b085532425 | ||
|
f385e080ab | ||
|
633214baa8 | ||
|
fc10f5676e | ||
|
987cfe2c6d | ||
|
67ea684d1d | ||
|
e5a04489b1 | ||
|
65ef870e17 | ||
|
2e0ae9153f | ||
|
3843366e7b | ||
|
c830bc7d81 | ||
|
ca6ee0c209 | ||
|
17cb59d75a | ||
|
5e87ce9f65 | ||
|
a2643cc133 | ||
|
f85ee31b4b | ||
|
5914b97f5e | ||
|
019f226b5e | ||
|
92b4e39b21 | ||
|
5f8ae644ff | ||
|
761f536b75 |
@ -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>
|
||||
|
||||
@ -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.
|
||||
@ -293,27 +291,66 @@ bool operator!=(const FineGrainedAccessPermissions &first, const FineGrainedAcce
|
||||
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,
|
||||
const FineGrainedAccessPermissions &fine_grained_access_permissions)
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler)
|
||||
: rolename_(utils::ToLowerCase(rolename)),
|
||||
permissions_(permissions),
|
||||
fine_grained_access_permissions_(fine_grained_access_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 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 +359,27 @@ 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, 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,
|
||||
const FineGrainedAccessPermissions &fine_grained_access_permissions)
|
||||
const 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_(fine_grained_access_handler) {}
|
||||
|
||||
bool User::CheckPassword(const std::string &password) {
|
||||
if (password_hash_.empty()) return true;
|
||||
@ -385,41 +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::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 +493,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 +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"]);
|
||||
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
|
||||
@ -119,18 +120,44 @@ bool operator==(const FineGrainedAccessPermissions &first, const FineGrainedAcce
|
||||
|
||||
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,
|
||||
const FineGrainedAccessPermissions &fine_grained_access_permissions);
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler);
|
||||
|
||||
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 +169,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);
|
||||
@ -153,7 +180,7 @@ class User final {
|
||||
User(const std::string &username);
|
||||
|
||||
User(const std::string &username, const std::string &password_hash, const Permissions &permissions,
|
||||
const FineGrainedAccessPermissions &fine_grained_access_permissions);
|
||||
const FineGrainedAccessHandler &fine_grained_access_handler);
|
||||
|
||||
/// @throw AuthException if unable to verify the password.
|
||||
bool CheckPassword(const std::string &password);
|
||||
@ -166,14 +193,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 +216,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_;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
@ -771,8 +771,8 @@ class AuthQueryHandler final : public memgraph::query::AuthQueryHandler {
|
||||
|
||||
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 +782,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 +793,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 +806,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 +828,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);
|
||||
@ -877,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_;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -14,7 +14,6 @@
|
||||
#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 +72,8 @@ struct ExecutionContext {
|
||||
ExecutionStats execution_stats;
|
||||
TriggerContextCollector *trigger_context_collector{nullptr};
|
||||
utils::AsyncTimer timer;
|
||||
FineGrainedAccessChecker *fine_grained_access_checker{nullptr};
|
||||
AuthChecker *auth_checker{nullptr};
|
||||
memgraph::auth::User *user{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,
|
||||
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)
|
||||
(edgeTypes "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> edgeTypes)
|
||||
: action_(action),
|
||||
user_(user),
|
||||
role_(role),
|
||||
user_or_role_(user_or_role),
|
||||
password_(password),
|
||||
privileges_(privileges),
|
||||
labels_(labels) {}
|
||||
labels_(labels),
|
||||
edgetypes_(edgeTypes) {}
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
|
@ -1277,7 +1277,9 @@ 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()) {
|
||||
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)));
|
||||
@ -1299,7 +1301,9 @@ 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()) {
|
||||
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)));
|
||||
@ -1321,7 +1325,9 @@ 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()) {
|
||||
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)));
|
||||
@ -1347,6 +1353,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
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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"
|
||||
@ -260,24 +260,6 @@ class ReplQueryHandler final : public query::ReplicationQueryHandler {
|
||||
private:
|
||||
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,
|
||||
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 +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);
|
||||
|
||||
@ -299,6 +282,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->edgetypes_;
|
||||
std::vector<std::string> labels = auth_query->labels_;
|
||||
auto password = EvaluateOptionalExpression(auth_query->password_, &evaluator);
|
||||
|
||||
@ -312,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:
|
||||
@ -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,8 @@ 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_.user = interpreter_context->auth->GetUser(*username);
|
||||
ctx_.auth_checker = interpreter_context->auth_checker;
|
||||
}
|
||||
#endif
|
||||
if (interpreter_context->config.execution_timeout_sec > 0) {
|
||||
@ -1146,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) {
|
||||
@ -2281,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();
|
||||
@ -2336,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()));
|
||||
|
@ -104,15 +104,15 @@ class AuthQueryHandler {
|
||||
|
||||
/// @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 };
|
||||
|
@ -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(),
|
||||
[¤t_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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
#
|
||||
|
13
tests/integration/lba/CMakeLists.txt
Normal file
13
tests/integration/lba/CMakeLists.txt
Normal 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)
|
59
tests/integration/lba/filtering.cpp
Normal file
59
tests/integration/lba/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;
|
||||
}
|
129
tests/integration/lba/runner.py
Normal file
129
tests/integration/lba/runner.py
Normal 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)
|
84
tests/integration/lba/tester.cpp
Normal file
84
tests/integration/lba/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;
|
||||
}
|
@ -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");
|
||||
|
@ -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__)
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user