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:
Marin Tomic 2018-08-16 10:13:04 +02:00
parent b448db245b
commit 327c3c5d9b
8 changed files with 267 additions and 12 deletions

View File

@ -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

View 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

View File

@ -0,0 +1,8 @@
#pragma once
#include "query/frontend/ast/ast.hpp"
namespace query {
std::vector<AuthQuery::Privilege> GetRequiredPrivileges(
const AstStorage &ast_storage);
}

View File

@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -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) \

View 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));
}
}