Compare commits

...

34 Commits

Author SHA1 Message Date
Boris Tasevski
3f5cb9c81d remove duplicate 2022-08-04 10:54:56 +02:00
Boris Tasevski
ec68d53404 merge epic 2022-08-04 10:18:58 +02:00
niko4299
a5cfc617fd dbaAccesor argument 2022-07-20 12:22:05 +02:00
Boris Tasevski
3c87a5d939 merge 2022-07-19 15:30:55 +02:00
Boris Tasevski
3a183986c9 improved Has method in LabelPermissions; LabelChecker lambda reworked a bit 2022-07-19 15:30:04 +02:00
niko4299
db655dab5e refactor 2022-07-19 10:26:04 +02:00
niko4299
efa64fc864 Filtering 2022-07-18 15:28:52 +02:00
niko4299
19dff1ae5c LabelChecker fixed 2022-07-12 12:07:28 +02:00
Boris Tasevski
41ae33c671 remote changes merged 2022-07-11 17:35:54 +02:00
Boris Tasevski
10f2c416bd code updated 2022-07-11 17:35:15 +02:00
niko4299
216abaa0ef without username can see all 2022-07-11 17:13:43 +02:00
niko4299
b0af87ed9b Added LabelChecker and dummy filtration 2022-07-11 17:09:47 +02:00
Boris Tasevski
5b92aae0d3 merge branch T610-FL-Add-Support-Label-Permissions-In-Model-NIKO 2022-07-11 09:20:35 +02:00
Boris Tasevski
6f2aaee91b merge 2022-07-08 15:34:11 +02:00
niko4299
83aa71a29f interpreter.cpp 286 line convert vector label strings to vector LabelId, possible changes to LabelPermission 2022-07-08 10:59:54 +02:00
Boris Taševski
a821827dc3 Merge pull request #436 from memgraph/T610-FL-Add-Support-For-Label-Permissions-In-Model
T610 fl add support for label permissions in model
2022-07-08 10:47:41 +02:00
Boris Tasevski
ace8608dd7 add missing methods to Role; Removed wrong check 2022-07-08 10:22:49 +02:00
niko4299
c2a1328dcc added Boris class 2022-07-07 16:03:22 +02:00
niko4299
b63db202d6 uncommented 2022-07-07 13:21:05 +02:00
niko4299
1abe8f8bfc Labels defined with colon 2022-07-07 13:15:01 +02:00
niko4299
4366085d89 GRANT, DENY, REVOKE all saving to rocksdb and working 2022-07-07 11:26:43 +02:00
Boris Tasevski
d626b49cc4 no return fixed; removed explicit keyword from LabelPermissions 2022-07-06 15:44:28 +02:00
niko4299
9369ae9085 My version with map, will test tomorrow 2022-07-06 14:50:00 +02:00
Boris Tasevski
d657a77a57 initial functionalities for users and roles implemented 2022-07-06 11:49:00 +02:00
Boris Tasevski
20da673583 implement LabelPermissions specific methods 2022-07-06 09:45:21 +02:00
Boris Tasevski
ca95ac253b added LabelPermission class to model.hpp; added dummy implementation to model.cpp 2022-07-05 16:36:04 +02:00
Boris Tasevski
b1c06e4d9f m 2022-07-05 15:40:31 +02:00
josipmrden
86a15331d1 Added saving of labels to AuthQuery 2022-07-04 16:49:23 +02:00
josipmrden
0c8b35b151 Added accepting visiting privilege to labels 2022-07-04 14:25:28 +02:00
josipmrden
11d60c203e Updated CypherLexer for LABELS 2022-07-04 14:16:50 +02:00
josipmrden
38c0a08342 Updated case which adds LABELS as Permissions 2022-07-04 14:11:30 +02:00
josipmrden
7e1d39bf86 Updated switch cases with privileges and permissions 2022-07-04 13:59:17 +02:00
josipmrden
dd85b428bf Updated lcp file 2022-07-04 13:54:36 +02:00
josipmrden
2f9ed0146e Updated lexer for adding privileges over labels 2022-07-04 13:49:54 +02:00
14 changed files with 169 additions and 52 deletions

43
libs/antlr4.patch Normal file
View File

@@ -0,0 +1,43 @@
diff --git a/runtime/Cpp/runtime/CMakeLists.txt b/runtime/Cpp/runtime/CMakeLists.txt
index a8503bb..11362cf 100644
--- a/runtime/Cpp/runtime/CMakeLists.txt
+++ b/runtime/Cpp/runtime/CMakeLists.txt
@@ -5,8 +5,8 @@ set(THIRDPARTY_DIR ${CMAKE_BINARY_DIR}/runtime/thirdparty)
set(UTFCPP_DIR ${THIRDPARTY_DIR}/utfcpp)
ExternalProject_Add(
utfcpp
- GIT_REPOSITORY "git://github.com/nemtrif/utfcpp"
- GIT_TAG "v3.1.1"
+ GIT_REPOSITORY "https://github.com/nemtrif/utfcpp"
+ GIT_TAG "v3.2.1"
SOURCE_DIR ${UTFCPP_DIR}
UPDATE_DISCONNECTED 1
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${UTFCPP_DIR}/install -Dgtest_force_shared_crt=ON
@@ -118,7 +118,7 @@ set_target_properties(antlr4_static
ARCHIVE_OUTPUT_DIRECTORY ${LIB_OUTPUT_DIR}
COMPILE_FLAGS "${disabled_compile_warnings} ${extra_static_compile_flags}")
-install(TARGETS antlr4_shared
+install(TARGETS antlr4_shared OPTIONAL
DESTINATION lib
EXPORT antlr4-targets)
install(TARGETS antlr4_static
diff --git a/runtime/Cpp/runtime/src/support/Any.h b/runtime/Cpp/runtime/src/support/Any.h
index 468db98..65a473b 100644
--- a/runtime/Cpp/runtime/src/support/Any.h
+++ b/runtime/Cpp/runtime/src/support/Any.h
@@ -122,12 +122,12 @@ private:
}
private:
- template<int N = 0, typename std::enable_if<N == N && std::is_nothrow_copy_constructible<T>::value, int>::type = 0>
+ template<int N = 0, typename std::enable_if<N == N && std::is_copy_constructible<T>::value, int>::type = 0>
Base* clone() const {
return new Derived<T>(value);
}
- template<int N = 0, typename std::enable_if<N == N && !std::is_nothrow_copy_constructible<T>::value, int>::type = 0>
+ template<int N = 0, typename std::enable_if<N == N && !std::is_copy_constructible<T>::value, int>::type = 0>
Base* clone() const {
return nullptr;
}

View File

@@ -8,7 +8,10 @@
#include "auth/models.hpp"
#include <algorithm>
#include <iterator>
#include <regex>
#include <unordered_set>
#include <gflags/gflags.h>
@@ -84,6 +87,8 @@ std::string PermissionToString(Permission permission) {
return "MODULE_WRITE";
case Permission::WEBSOCKET:
return "WEBSOCKET";
case Permission::LABELS:
return "LABELS";
}
}
@@ -98,12 +103,9 @@ 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);
}
const std::string ASTERISK = "*";
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,8 +185,6 @@ 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) {}
@@ -332,7 +332,8 @@ Role Role::Deserialize(const nlohmann::json &data) {
}
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_permissions_ == second.fine_grained_access_permissions_;
}
User::User(const std::string &username) : username_(utils::ToLowerCase(username)) {}
@@ -385,8 +386,8 @@ 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_;
}
@@ -453,6 +454,9 @@ User User::Deserialize(const nlohmann::json &data) {
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.fine_grained_access_permissions_ == second.fine_grained_access_permissions_ &&
first.role_ == second.role_;
}
} // namespace memgraph::auth

View File

@@ -13,6 +13,7 @@
#include <unordered_set>
#include <json/json.hpp>
#include <unordered_set>
namespace memgraph::auth {
// These permissions must have values that are applicable for usage in a
@@ -39,7 +40,8 @@ enum class Permission : uint64_t {
STREAM = 1U << 17U,
MODULE_READ = 1U << 18U,
MODULE_WRITE = 1U << 19U,
WEBSOCKET = 1U << 20U
WEBSOCKET = 1U << 20U,
LABELS = 1U << 21U
};
// clang-format on
@@ -193,4 +195,5 @@ class User final {
};
bool operator==(const User &first, const User &second);
} // namespace memgraph::auth

View File

@@ -18,19 +18,24 @@ roles_config = config["roles"]
# Initialize LDAP server.
tls = None
if server_config["encryption"] != "disabled":
cert_file = server_config["cert_file"] if server_config["cert_file"] \
else None
cert_file = server_config["cert_file"] if server_config["cert_file"] else None
key_file = server_config["key_file"] if server_config["key_file"] else None
ca_file = server_config["ca_file"] if server_config["ca_file"] else None
validate = ssl.CERT_REQUIRED if server_config["validate_cert"] \
else ssl.CERT_NONE
tls = ldap3.Tls(local_private_key_file=key_file,
local_certificate_file=cert_file,
ca_certs_file=ca_file,
validate=validate)
validate = ssl.CERT_REQUIRED if server_config["validate_cert"] else ssl.CERT_NONE
tls = ldap3.Tls(
local_private_key_file=key_file,
local_certificate_file=cert_file,
ca_certs_file=ca_file,
validate=validate,
)
use_ssl = server_config["encryption"] == "ssl"
server = ldap3.Server(server_config["host"], port=server_config["port"],
tls=tls, use_ssl=use_ssl, get_info=ldap3.ALL)
server = ldap3.Server(
server_config["host"],
port=server_config["port"],
tls=tls,
use_ssl=use_ssl,
get_info=ldap3.ALL,
)
# Main authentication/authorization function.
@@ -40,14 +45,12 @@ def authenticate(username, password):
return {"authenticated": False, "role": ""}
# Create the DN of the user
dn = users_config["prefix"] + ldap3.utils.dn.escape_rdn(username) + \
users_config["suffix"]
dn = users_config["prefix"] + ldap3.utils.dn.escape_rdn(username) + users_config["suffix"]
# Bind to the server
conn = ldap3.Connection(server, dn, password)
if server_config["encryption"] == "starttls" and not conn.start_tls():
print("ERROR: Couldn't issue STARTTLS to the LDAP server!",
file=sys.stderr)
print("ERROR: Couldn't issue STARTTLS to the LDAP server!", file=sys.stderr)
return {"authenticated": False, "role": ""}
if not conn.bind():
return {"authenticated": False, "role": ""}
@@ -56,25 +59,32 @@ def authenticate(username, password):
if roles_config["root_dn"] != "":
# search for role
search_filter = "(&(objectclass={objclass})({attr}={value}))".format(
objclass=roles_config["root_objectclass"],
attr=roles_config["user_attribute"],
value=ldap3.utils.conv.escape_filter_chars(dn))
succ = conn.search(roles_config["root_dn"], search_filter,
search_scope=ldap3.LEVEL,
attributes=[roles_config["role_attribute"]])
objclass=roles_config["root_objectclass"],
attr=roles_config["user_attribute"],
value=ldap3.utils.conv.escape_filter_chars(dn),
)
succ = conn.search(
roles_config["root_dn"],
search_filter,
search_scope=ldap3.LEVEL,
attributes=[roles_config["role_attribute"]],
)
if not succ or len(conn.entries) == 0:
return {"authenticated": True, "role": ""}
if len(conn.entries) > 1:
roles = list(map(lambda x: x[roles_config["role_attribute"]].value,
conn.entries))
roles = list(map(lambda x: x[roles_config["role_attribute"]].value, conn.entries))
# Because we don't know exactly which role the user should have
# we authorize the user with an empty role.
print("WARNING: Found more than one role for "
"user '" + username + "':", ", ".join(roles) + "!",
file=sys.stderr)
print(
"WARNING: Found more than one role for " "user '" + username + "':",
", ".join(roles) + "!",
file=sys.stderr,
)
return {"authenticated": True, "role": ""}
return {"authenticated": True,
"role": conn.entries[0][roles_config["role_attribute"]].value}
return {
"authenticated": True,
"role": conn.entries[0][roles_config["role_attribute"]].value,
}
else:
return {"authenticated": True, "role": ""}

View File

@@ -57,6 +57,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
return auth::Permission::MODULE_WRITE;
case query::AuthQuery::Privilege::WEBSOCKET:
return auth::Permission::WEBSOCKET;
case query::AuthQuery::Privilege::LABELS:
return auth::Permission::LABELS;
}
}
} // namespace memgraph::glue

View File

@@ -38,6 +38,7 @@
#include "helpers.hpp"
#include "py/py.hpp"
#include "query/auth_checker.hpp"
#include "query/db_accessor.hpp"
#include "query/discard_value_stream.hpp"
#include "query/exceptions.hpp"
#include "query/frontend/ast/ast.hpp"

View File

@@ -13,9 +13,11 @@
#include <type_traits>
#include "auth/models.hpp"
#include "query/common.hpp"
#include "query/fine_grained_access_checker.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/label_checker.hpp"
#include "query/metadata.hpp"
#include "query/parameters.hpp"
#include "query/plan/profile.hpp"

View File

@@ -2240,6 +2240,7 @@ cpp<#
(user "std::string" :scope :public)
(role "std::string" :scope :public)
(user-or-role "std::string" :scope :public)
(password "Expression *" :initval "nullptr" :scope :public
:slk-save #'slk-save-ast-pointer
:slk-load (slk-load-ast-pointer "Expression"))
@@ -2255,7 +2256,7 @@ cpp<#
(lcp:define-enum privilege
(create delete match merge set remove index stats auth constraint
dump replication durability read_file free_memory trigger config stream module_read module_write
websocket)
websocket labels)
(:serialize))
#>cpp
AuthQuery() = default;
@@ -2298,7 +2299,8 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
AuthQuery::Privilege::FREE_MEMORY, AuthQuery::Privilege::TRIGGER,
AuthQuery::Privilege::CONFIG, AuthQuery::Privilege::STREAM,
AuthQuery::Privilege::MODULE_READ, AuthQuery::Privilege::MODULE_WRITE,
AuthQuery::Privilege::WEBSOCKET};
AuthQuery::Privilege::WEBSOCKET,
AuthQuery::Privilege::LABELS};
cpp<#
(lcp:define-class info-query (query)

View File

@@ -1372,6 +1372,10 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
if (ctx->MODULE_READ()) return AuthQuery::Privilege::MODULE_READ;
if (ctx->MODULE_WRITE()) return AuthQuery::Privilege::MODULE_WRITE;
if (ctx->WEBSOCKET()) return AuthQuery::Privilege::WEBSOCKET;
if (ctx->LABELS()) {
// fill labels in authquery
return AuthQuery::Privilege::LABELS;
}
LOG_FATAL("Should not get here - unknown privilege!");
}

View File

@@ -473,16 +473,16 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
*/
antlrcpp::Any visitPrivilege(MemgraphCypher::PrivilegeContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitShowPrivileges(MemgraphCypher::ShowPrivilegesContext *ctx) override;
/**
* @return AuthQuery::LabelList
*/
antlrcpp::Any visitLabelList(MemgraphCypher::LabelListContext *ctx) override;
/**
* @return AuthQuery*
*/
antlrcpp::Any visitShowPrivileges(MemgraphCypher::ShowPrivilegesContext *ctx) override;
/**
* @return AuthQuery*
*/

View File

@@ -37,6 +37,7 @@
#include "query/frontend/semantic/required_privileges.hpp"
#include "query/frontend/semantic/symbol_generator.hpp"
#include "query/interpret/eval.hpp"
#include "query/label_checker.hpp"
#include "query/metadata.hpp"
#include "query/plan/planner.hpp"
#include "query/plan/profile.hpp"
@@ -292,6 +293,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);
@@ -1146,6 +1148,7 @@ PreparedQuery PrepareCypherQuery(ParsedQuery parsed_query, std::map<std::string,
EvaluationContext evaluation_context;
evaluation_context.timestamp = QueryTimestamp();
evaluation_context.parameters = parsed_query.parameters;
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, dba, storage::View::OLD);
const auto memory_limit = EvaluateMemoryLimit(&evaluator, cypher_query->memory_limit_, cypher_query->memory_scale_);
if (memory_limit) {

View File

@@ -91,6 +91,9 @@ class AuthQueryHandler {
/// @throw QueryRuntimeException if an error ocurred.
virtual std::vector<TypedValue> GetUsernamesForRole(const std::string &rolename) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual memgraph::auth::User *GetUser(const std::string &username) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void SetRole(const std::string &username, const std::string &rolename) = 0;
@@ -99,9 +102,6 @@ class AuthQueryHandler {
virtual std::vector<std::vector<TypedValue>> GetPrivileges(const std::string &user_or_role) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual memgraph::auth::User *GetUser(const std::string &username) = 0;
/// @throw QueryRuntimeException if an error ocurred.
virtual void GrantPrivilege(const std::string &user_or_role, const std::vector<AuthQuery::Privilege> &privileges,
const std::vector<std::string> &labels) = 0;

View File

@@ -0,0 +1,24 @@
// 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 LabelChecker {
public:
virtual bool IsUserAuthorized(const std::vector<memgraph::storage::LabelId> &label,
memgraph::query::DbAccessor *dba) const = 0;
};
} // namespace memgraph::query

View File

@@ -32,6 +32,7 @@
#include "query/frontend/ast/ast.hpp"
#include "query/frontend/semantic/symbol_table.hpp"
#include "query/interpret/eval.hpp"
#include "query/label_checker.hpp"
#include "query/path.hpp"
#include "query/plan/scoped_profile.hpp"
#include "query/procedure/cypher_types.hpp"
@@ -394,8 +395,8 @@ class ScanAllCursor : public Cursor {
while (!vertices_ || vertices_it_.value() == vertices_.value().end()) {
if (!input_cursor_->Pull(frame, context)) return false;
// We need a getter function, because in case of exhausting a lazy
// iterable, we cannot simply reset it by calling begin().
// We need a getter function, because in case of exhausting a lazy iterable,
// we cannot simply reset it by calling begin().
auto next_vertices = get_vertices_(frame, context);
if (!next_vertices) continue;
// Since vertices iterator isn't nothrow_move_assignable, we have to use
@@ -405,11 +406,29 @@ class ScanAllCursor : public Cursor {
vertices_it_.emplace(vertices_.value().begin());
}
#ifdef MG_ENTERPRISE
FilterNodes(context.label_checker, context.db_accessor);
if (vertices_it_.value() == vertices_.value().end()) return false;
#endif
frame[output_symbol_] = *vertices_it_.value();
++vertices_it_.value();
return true;
}
void FilterNodes(const LabelChecker *label_checker, DbAccessor *dba) {
if (!label_checker) return;
while (vertices_it_.value() != vertices_.value().end()) {
VertexAccessor vertex = *vertices_it_.value();
auto vertex_labels = vertex.Labels(memgraph::storage::View::NEW).GetValue();
if (label_checker->IsUserAuthorized(vertex_labels, dba)) {
break;
}
++vertices_it_.value();
}
}
void Shutdown() override { input_cursor_->Shutdown(); }
void Reset() override {