Add required privileges for query to Results
Reviewers: mferencevic, buda Reviewed By: mferencevic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1537
This commit is contained in:
parent
b448db245b
commit
327c3c5d9b
@ -47,6 +47,7 @@ set(memgraph_src_files
|
||||
query/common.cpp
|
||||
query/frontend/ast/ast.cpp
|
||||
query/frontend/ast/cypher_main_visitor.cpp
|
||||
query/frontend/semantic/required_privileges.cpp
|
||||
query/frontend/semantic/symbol_generator.cpp
|
||||
query/frontend/stripped.cpp
|
||||
query/interpret/awesome_memgraph_functions.cpp
|
||||
|
103
src/query/frontend/semantic/required_privileges.cpp
Normal file
103
src/query/frontend/semantic/required_privileges.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
|
||||
namespace query {
|
||||
|
||||
class PrivilegeExtractor : public HierarchicalTreeVisitor {
|
||||
public:
|
||||
using HierarchicalTreeVisitor::PostVisit;
|
||||
using HierarchicalTreeVisitor::PreVisit;
|
||||
using HierarchicalTreeVisitor::Visit;
|
||||
|
||||
std::vector<AuthQuery::Privilege> privileges() { return privileges_; }
|
||||
|
||||
bool PreVisit(Create &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::CREATE);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(Delete &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::DELETE);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(Match &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::MATCH);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(Merge &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::MERGE);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(SetProperty &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::SET);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(SetProperties &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::SET);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(SetLabels &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::SET);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(RemoveProperty &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::REMOVE);
|
||||
return false;
|
||||
}
|
||||
bool PreVisit(RemoveLabels &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::REMOVE);
|
||||
return false;
|
||||
}
|
||||
bool Visit(Identifier &) override { return true; }
|
||||
bool Visit(PrimitiveLiteral &) override { return true; }
|
||||
bool Visit(ParameterLookup &) override { return true; }
|
||||
|
||||
bool Visit(CreateIndex &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::INDEX);
|
||||
return true;
|
||||
}
|
||||
bool Visit(AuthQuery &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::AUTH);
|
||||
return true;
|
||||
}
|
||||
bool Visit(CreateStream &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::STREAM);
|
||||
return true;
|
||||
}
|
||||
bool Visit(DropStream &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::STREAM);
|
||||
return true;
|
||||
}
|
||||
bool Visit(ShowStreams &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::STREAM);
|
||||
return true;
|
||||
}
|
||||
bool Visit(StartStopStream &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::STREAM);
|
||||
return true;
|
||||
}
|
||||
bool Visit(StartStopAllStreams &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::STREAM);
|
||||
return true;
|
||||
}
|
||||
bool Visit(TestStream &) override {
|
||||
AddPrivilege(AuthQuery::Privilege::STREAM);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void AddPrivilege(AuthQuery::Privilege privilege) {
|
||||
if (!utils::Contains(privileges_, privilege)) {
|
||||
privileges_.push_back(privilege);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AuthQuery::Privilege> privileges_;
|
||||
};
|
||||
|
||||
std::vector<AuthQuery::Privilege> GetRequiredPrivileges(
|
||||
const AstStorage &ast_storage) {
|
||||
PrivilegeExtractor extractor;
|
||||
ast_storage.query()->Accept(extractor);
|
||||
return extractor.privileges();
|
||||
}
|
||||
|
||||
} // namespace query
|
8
src/query/frontend/semantic/required_privileges.hpp
Normal file
8
src/query/frontend/semantic/required_privileges.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
|
||||
namespace query {
|
||||
std::vector<AuthQuery::Privilege> GetRequiredPrivileges(
|
||||
const AstStorage &ast_storage);
|
||||
}
|
@ -8,6 +8,7 @@
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "query/frontend/semantic/required_privileges.hpp"
|
||||
#include "query/frontend/semantic/symbol_generator.hpp"
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/vertex_count_cache.hpp"
|
||||
@ -80,6 +81,10 @@ Interpreter::Results Interpreter::operator()(
|
||||
}
|
||||
ctx.parameters_.Add(param_pair.first, param_it->second);
|
||||
}
|
||||
AstStorage ast_storage = QueryToAst(stripped, ctx);
|
||||
// TODO: Maybe cache required privileges to improve performance on very simple
|
||||
// queries.
|
||||
auto required_privileges = query::GetRequiredPrivileges(ast_storage);
|
||||
auto frontend_time = frontend_timer.Elapsed();
|
||||
|
||||
// Try to get a cached plan. Note that this local shared_ptr might be the only
|
||||
@ -96,8 +101,9 @@ Interpreter::Results Interpreter::operator()(
|
||||
}
|
||||
utils::Timer planning_timer;
|
||||
if (!plan) {
|
||||
plan = plan_cache_access.insert(stripped.hash(), QueryToPlan(stripped, ctx))
|
||||
.first->second;
|
||||
plan =
|
||||
plan_cache_access.insert(stripped.hash(), AstToPlan(ast_storage, ctx))
|
||||
.first->second;
|
||||
}
|
||||
auto planning_time = planning_timer.Elapsed();
|
||||
|
||||
@ -128,12 +134,11 @@ Interpreter::Results Interpreter::operator()(
|
||||
}
|
||||
|
||||
return Results(std::move(ctx), plan, std::move(cursor), output_symbols,
|
||||
header, summary, plan_cache_);
|
||||
header, summary, plan_cache_, required_privileges);
|
||||
}
|
||||
|
||||
std::shared_ptr<Interpreter::CachedPlan> Interpreter::QueryToPlan(
|
||||
const StrippedQuery &stripped, Context &ctx) {
|
||||
AstStorage ast_storage = QueryToAst(stripped, ctx);
|
||||
std::shared_ptr<Interpreter::CachedPlan> Interpreter::AstToPlan(
|
||||
AstStorage &ast_storage, Context &ctx) {
|
||||
SymbolGenerator symbol_generator(ctx.symbol_table_);
|
||||
ast_storage.query()->Accept(symbol_generator);
|
||||
|
||||
|
@ -74,7 +74,8 @@ class Interpreter {
|
||||
Results(Context ctx, std::shared_ptr<CachedPlan> plan,
|
||||
std::unique_ptr<query::plan::Cursor> cursor,
|
||||
std::vector<Symbol> output_symbols, std::vector<std::string> header,
|
||||
std::map<std::string, TypedValue> summary, PlanCacheT &plan_cache)
|
||||
std::map<std::string, TypedValue> summary, PlanCacheT &plan_cache,
|
||||
std::vector<AuthQuery::Privilege> privileges)
|
||||
: ctx_(std::move(ctx)),
|
||||
plan_(plan),
|
||||
cursor_(std::move(cursor)),
|
||||
@ -82,7 +83,8 @@ class Interpreter {
|
||||
output_symbols_(output_symbols),
|
||||
header_(header),
|
||||
summary_(summary),
|
||||
plan_cache_(plan_cache) {}
|
||||
plan_cache_(plan_cache),
|
||||
privileges_(std::move(privileges)) {}
|
||||
|
||||
public:
|
||||
Results(const Results &) = delete;
|
||||
@ -137,6 +139,10 @@ class Interpreter {
|
||||
const std::vector<std::string> &header() { return header_; }
|
||||
const std::map<std::string, TypedValue> &summary() { return summary_; }
|
||||
|
||||
const std::vector<AuthQuery::Privilege> &privileges() {
|
||||
return privileges_;
|
||||
}
|
||||
|
||||
private:
|
||||
Context ctx_;
|
||||
std::shared_ptr<CachedPlan> plan_;
|
||||
@ -150,6 +156,8 @@ class Interpreter {
|
||||
double execution_time_{0};
|
||||
// Gets invalidated after if an index has been built.
|
||||
PlanCacheT &plan_cache_;
|
||||
|
||||
std::vector<AuthQuery::Privilege> privileges_;
|
||||
};
|
||||
|
||||
explicit Interpreter(database::GraphDb &db);
|
||||
@ -185,9 +193,8 @@ class Interpreter {
|
||||
// Optional, not null only in a distributed master.
|
||||
distributed::PlanDispatcher *plan_dispatcher_{nullptr};
|
||||
|
||||
// stripped query -> CachedPlan
|
||||
std::shared_ptr<CachedPlan> QueryToPlan(const StrippedQuery &stripped,
|
||||
Context &ctx);
|
||||
// high level tree -> CachedPlan
|
||||
std::shared_ptr<CachedPlan> AstToPlan(AstStorage &ast_storage, Context &ctx);
|
||||
// stripped query -> high level tree
|
||||
AstStorage QueryToAst(const StrippedQuery &stripped, Context &ctx);
|
||||
|
||||
|
@ -169,6 +169,9 @@ target_link_libraries(${test_prefix}query_plan_match_filter_return memgraph_lib
|
||||
add_unit_test(query_planner.cpp)
|
||||
target_link_libraries(${test_prefix}query_planner memgraph_lib kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(query_required_privileges.cpp)
|
||||
target_link_libraries(${test_prefix}query_required_privileges memgraph_lib kvstore_dummy_lib)
|
||||
|
||||
add_unit_test(query_semantic.cpp)
|
||||
target_link_libraries(${test_prefix}query_semantic memgraph_lib kvstore_dummy_lib)
|
||||
|
||||
|
@ -576,7 +576,7 @@ auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match,
|
||||
list, expr)
|
||||
#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \
|
||||
storage.Create<query::AuthQuery>((action), (user), (role), (user_or_role), \
|
||||
LITERAL(password), (privileges))
|
||||
password, (privileges))
|
||||
#define DROP_USER(usernames) storage.Create<query::DropUser>((usernames))
|
||||
#define CREATE_STREAM(stream_name, stream_uri, stream_topic, transform_uri, \
|
||||
batch_interval, batch_size) \
|
||||
|
128
tests/unit/query_required_privileges.cpp
Normal file
128
tests/unit/query_required_privileges.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "query/frontend/semantic/required_privileges.hpp"
|
||||
#include "storage/types.hpp"
|
||||
|
||||
#include "query_common.hpp"
|
||||
|
||||
using namespace query;
|
||||
|
||||
class FakeDbAccessor {};
|
||||
|
||||
storage::EdgeType EDGE_TYPE(0);
|
||||
storage::Label LABEL_0(0);
|
||||
storage::Label LABEL_1(1);
|
||||
storage::Property PROP_0(0);
|
||||
|
||||
using ::testing::UnorderedElementsAre;
|
||||
|
||||
class TestPrivilegeExtractor : public ::testing::Test {
|
||||
protected:
|
||||
AstStorage storage;
|
||||
FakeDbAccessor dba;
|
||||
};
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CreateNode) {
|
||||
QUERY(SINGLE_QUERY(CREATE(PATTERN(NODE("n")))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::CREATE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeDelete) {
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), DELETE(IDENT("n"))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::DELETE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeReturn) {
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), RETURN("n")));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchCreateExpand) {
|
||||
QUERY(SINGLE_QUERY(
|
||||
MATCH(PATTERN(NODE("n"))),
|
||||
CREATE(PATTERN(NODE("n"),
|
||||
EDGE("r", EdgeAtom::Direction::OUT, {EDGE_TYPE}),
|
||||
NODE("m")))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::CREATE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeSetLabels) {
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET("n", {LABEL_0, LABEL_1})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::SET));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeSetProperty) {
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
SET(PROPERTY_LOOKUP("n", {"prop", PROP_0}), LITERAL(42))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::SET));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeSetProperties) {
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), SET("n", LIST())));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::SET));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeRemoveLabels) {
|
||||
QUERY(
|
||||
SINGLE_QUERY(MATCH(PATTERN(NODE("n"))), REMOVE("n", {LABEL_0, LABEL_1})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::REMOVE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, MatchNodeRemoveProperty) {
|
||||
QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n"))),
|
||||
REMOVE(PROPERTY_LOOKUP("n", {"prop", PROP_0}))));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::MATCH,
|
||||
AuthQuery::Privilege::REMOVE));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, CreateIndex) {
|
||||
QUERY(SINGLE_QUERY(CREATE_INDEX_ON(LABEL_0, PROP_0)));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::INDEX));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, AuthQuery) {
|
||||
QUERY(SINGLE_QUERY(AUTH_QUERY(AuthQuery::Action::CREATE_ROLE, "", "role", "",
|
||||
nullptr, std::vector<AuthQuery::Privilege>{})));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::AUTH));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, StreamQuery) {
|
||||
std::string stream_name("kafka");
|
||||
std::string stream_uri("localhost:1234");
|
||||
std::string stream_topic("tropik");
|
||||
std::string transform_uri("localhost:1234/file.py");
|
||||
|
||||
std::vector<Clause *> stream_clauses = {
|
||||
CREATE_STREAM(stream_name, stream_uri, stream_topic, transform_uri,
|
||||
nullptr, nullptr),
|
||||
DROP_STREAM(stream_name),
|
||||
SHOW_STREAMS,
|
||||
START_STREAM(stream_name, nullptr),
|
||||
STOP_STREAM(stream_name),
|
||||
START_ALL_STREAMS,
|
||||
STOP_ALL_STREAMS};
|
||||
|
||||
for (auto *stream_clause : stream_clauses) {
|
||||
QUERY(SINGLE_QUERY(stream_clause));
|
||||
EXPECT_THAT(GetRequiredPrivileges(storage),
|
||||
UnorderedElementsAre(AuthQuery::Privilege::STREAM));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user