From 2a5fce8464373c882b75c9697aefc4a6d7b8fb1d Mon Sep 17 00:00:00 2001 From: Marin Tomic Date: Mon, 6 Aug 2018 15:16:39 +0200 Subject: [PATCH] Add rest of user auth queries Reviewers: mferencevic, teon.banek Reviewed By: mferencevic, teon.banek Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1522 --- src/query/frontend/ast/ast.capnp | 56 ++-- src/query/frontend/ast/ast.cpp | 227 ++++++++++++---- src/query/frontend/ast/ast.hpp | 91 ++++--- src/query/frontend/ast/ast_visitor.hpp | 14 +- .../frontend/ast/cypher_main_visitor.cpp | 242 ++++++++++++++--- .../frontend/ast/cypher_main_visitor.hpp | 96 ++++++- .../opencypher/grammar/MemgraphCypher.g4 | 68 ++++- .../opencypher/grammar/MemgraphCypherLexer.g4 | 13 + .../frontend/semantic/symbol_generator.cpp | 4 +- .../frontend/semantic/symbol_generator.hpp | 3 +- src/query/interpret/eval.hpp | 3 +- src/query/plan/cost_estimator.hpp | 3 +- src/query/plan/distributed.cpp | 7 +- src/query/plan/operator.cpp | 193 +++++++------- src/query/plan/operator.lcp | 187 ++++++++----- src/query/plan/preprocess.hpp | 3 +- src/query/plan/rule_based_planner.cpp | 7 +- src/query/plan/rule_based_planner.hpp | 16 +- tests/manual/query_planner.cpp | 9 +- tests/unit/cypher_main_visitor.cpp | 251 +++++++++++++----- tests/unit/query_common.hpp | 7 +- tests/unit/query_planner.cpp | 116 ++++---- 22 files changed, 1106 insertions(+), 510 deletions(-) diff --git a/src/query/frontend/ast/ast.capnp b/src/query/frontend/ast/ast.capnp index aefb0c395..1c48259f1 100644 --- a/src/query/frontend/ast/ast.capnp +++ b/src/query/frontend/ast/ast.capnp @@ -132,14 +132,13 @@ struct Clause { merge @10 :Merge; unwind @11 :Unwind; createIndex @12 :CreateIndex; - modifyUser @13 :ModifyUser; - dropUser @14 :DropUser; - createStream @15 :CreateStream; - dropStream @16 :DropStream; - showStreams @17 :ShowStreams; - startStopStream @18 :StartStopStream; - startStopAllStreams @19 :StartStopAllStreams; - testStream @20 :TestStream; + authQuery @13 :AuthQuery; + createStream @14 :CreateStream; + dropStream @15 :DropStream; + showStreams @16 :ShowStreams; + startStopStream @17 :StartStopStream; + startStopAllStreams @18 :StartStopAllStreams; + testStream @19 :TestStream; } } @@ -398,14 +397,39 @@ struct CreateIndex { property @1 :Storage.Common; } -struct ModifyUser { - username @0 :Text; - password @1 :Tree; - isCreate @2 :Bool; -} - -struct DropUser { - usernames @0 :List(Text); +struct AuthQuery { + enum Action { + createRole @0; + dropRole @1; + showRoles @2; + createUser @3; + setPassword @4; + dropUser @5; + showUsers @6; + grantRole @7; + revokeRole @8; + grantPrivilege @9; + denyPrivilege @10; + revokePrivilege @11; + showGrants @12; + showRoleForUser @13; + showUsersForRole @14; + } + enum Privilege { + create @0; + delete @1; + match @2; + merge @3; + set @4; + auth @5; + stream @6; + } + action @0 :Action; + user @1 :Text; + role @2 :Text; + userOrRole @3 :Text; + password @4 :Tree; + privileges @5 :List(Privilege); } struct CreateStream { diff --git a/src/query/frontend/ast/ast.cpp b/src/query/frontend/ast/ast.cpp index b7171535d..ce604be2d 100644 --- a/src/query/frontend/ast/ast.cpp +++ b/src/query/frontend/ast/ast.cpp @@ -1268,13 +1268,9 @@ Clause *Clause::Construct(const capnp::Clause::Reader &reader, auto with_reader = reader.getWith(); return With::Construct(with_reader, storage); } - case capnp::Clause::MODIFY_USER: { - auto mu_reader = reader.getModifyUser(); - return ModifyUser::Construct(mu_reader, storage); - } - case capnp::Clause::DROP_USER: { - auto du_reader = reader.getDropUser(); - return DropUser::Construct(du_reader, storage); + case capnp::Clause::AUTH_QUERY: { + auto aq_reader = reader.getAuthQuery(); + return AuthQuery::Construct(aq_reader, storage); } case capnp::Clause::CREATE_STREAM: { auto cs_reader = reader.getCreateStream(); @@ -2067,69 +2063,188 @@ With *With::Construct(const capnp::With::Reader &reader, AstStorage *storage) { return storage->Create(); } -// ModifyUser. -void ModifyUser::Save(capnp::Clause::Builder *clause_builder, - std::vector *saved_uids) { +// AuthQuery. +void AuthQuery::Save(capnp::Clause::Builder *clause_builder, + std::vector *saved_uids) { Clause::Save(clause_builder, saved_uids); - auto builder = clause_builder->initModifyUser(); - ModifyUser::Save(&builder, saved_uids); + auto builder = clause_builder->initAuthQuery(); + AuthQuery::Save(&builder, saved_uids); } -void ModifyUser::Save(capnp::ModifyUser::Builder *builder, - std::vector *saved_uids) { - builder->setUsername(username_); +void AuthQuery::Save(capnp::AuthQuery::Builder *builder, + std::vector *saved_uids) { + switch (action_) { + case Action::CREATE_ROLE: + builder->setAction(capnp::AuthQuery::Action::CREATE_ROLE); + break; + case Action::DROP_ROLE: + builder->setAction(capnp::AuthQuery::Action::DROP_ROLE); + break; + case Action::SHOW_ROLES: + builder->setAction(capnp::AuthQuery::Action::SHOW_ROLES); + break; + case Action::CREATE_USER: + builder->setAction(capnp::AuthQuery::Action::CREATE_USER); + break; + case Action::SET_PASSWORD: + builder->setAction(capnp::AuthQuery::Action::SET_PASSWORD); + break; + case Action::DROP_USER: + builder->setAction(capnp::AuthQuery::Action::DROP_USER); + break; + case Action::SHOW_USERS: + builder->setAction(capnp::AuthQuery::Action::SHOW_USERS); + break; + case Action::GRANT_ROLE: + builder->setAction(capnp::AuthQuery::Action::GRANT_ROLE); + break; + case Action::REVOKE_ROLE: + builder->setAction(capnp::AuthQuery::Action::REVOKE_ROLE); + break; + case Action::GRANT_PRIVILEGE: + builder->setAction(capnp::AuthQuery::Action::GRANT_PRIVILEGE); + break; + case Action::DENY_PRIVILEGE: + builder->setAction(capnp::AuthQuery::Action::DENY_PRIVILEGE); + break; + case Action::REVOKE_PRIVILEGE: + builder->setAction(capnp::AuthQuery::Action::REVOKE_PRIVILEGE); + break; + case Action::SHOW_GRANTS: + builder->setAction(capnp::AuthQuery::Action::SHOW_GRANTS); + break; + case Action::SHOW_ROLE_FOR_USER: + builder->setAction(capnp::AuthQuery::Action::SHOW_ROLE_FOR_USER); + break; + case Action::SHOW_USERS_FOR_ROLE: + builder->setAction(capnp::AuthQuery::Action::SHOW_USERS_FOR_ROLE); + break; + } + builder->setUser(user_); + builder->setRole(role_); + builder->setUserOrRole(user_or_role_); if (password_) { - auto password_builder = builder->getPassword(); + auto password_builder = builder->initPassword(); password_->Save(&password_builder, saved_uids); } - builder->setIsCreate(is_create_); + ::capnp::List::Builder privileges_builder = + builder->initPrivileges(privileges_.size()); + for (size_t i = 0; i < privileges_.size(); ++i) { + switch (privileges_[i]) { + case Privilege::CREATE: + privileges_builder.set(i, capnp::AuthQuery::Privilege::CREATE); + break; + case Privilege::DELETE: + privileges_builder.set(i, capnp::AuthQuery::Privilege::DELETE); + break; + case Privilege::MATCH: + privileges_builder.set(i, capnp::AuthQuery::Privilege::MATCH); + break; + case Privilege::MERGE: + privileges_builder.set(i, capnp::AuthQuery::Privilege::MERGE); + break; + case Privilege::SET: + privileges_builder.set(i, capnp::AuthQuery::Privilege::SET); + break; + case Privilege::AUTH: + privileges_builder.set(i, capnp::AuthQuery::Privilege::AUTH); + break; + case Privilege::STREAM: + privileges_builder.set(i, capnp::AuthQuery::Privilege::STREAM); + break; + } + } } -void ModifyUser::Load(const capnp::Tree::Reader &base_reader, - AstStorage *storage, std::vector *loaded_uids) { +void AuthQuery::Load(const capnp::Tree::Reader &base_reader, + AstStorage *storage, std::vector *loaded_uids) { Clause::Load(base_reader, storage, loaded_uids); - auto reader = base_reader.getClause().getModifyUser(); - username_ = reader.getUsername(); - if (reader.hasPassword()) { - const auto password_reader = reader.getPassword(); + auto auth_reader = base_reader.getClause().getAuthQuery(); + switch (auth_reader.getAction()) { + case capnp::AuthQuery::Action::CREATE_ROLE: + action_ = Action::CREATE_ROLE; + break; + case capnp::AuthQuery::Action::DROP_ROLE: + action_ = Action::DROP_ROLE; + break; + case capnp::AuthQuery::Action::SHOW_ROLES: + action_ = Action::SHOW_ROLES; + break; + case capnp::AuthQuery::Action::CREATE_USER: + action_ = Action::CREATE_USER; + break; + case capnp::AuthQuery::Action::SET_PASSWORD: + action_ = Action::SET_PASSWORD; + break; + case capnp::AuthQuery::Action::DROP_USER: + action_ = Action::DROP_USER; + break; + case capnp::AuthQuery::Action::SHOW_USERS: + action_ = Action::SHOW_USERS; + break; + case capnp::AuthQuery::Action::GRANT_ROLE: + action_ = Action::GRANT_ROLE; + break; + case capnp::AuthQuery::Action::REVOKE_ROLE: + action_ = Action::REVOKE_ROLE; + break; + case capnp::AuthQuery::Action::GRANT_PRIVILEGE: + action_ = Action::GRANT_PRIVILEGE; + break; + case capnp::AuthQuery::Action::DENY_PRIVILEGE: + action_ = Action::DENY_PRIVILEGE; + break; + case capnp::AuthQuery::Action::REVOKE_PRIVILEGE: + action_ = Action::REVOKE_PRIVILEGE; + break; + case capnp::AuthQuery::Action::SHOW_GRANTS: + action_ = Action::SHOW_GRANTS; + break; + case capnp::AuthQuery::Action::SHOW_ROLE_FOR_USER: + action_ = Action::SHOW_ROLE_FOR_USER; + break; + case capnp::AuthQuery::Action::SHOW_USERS_FOR_ROLE: + action_ = Action::SHOW_USERS_FOR_ROLE; + break; + } + user_ = auth_reader.getUser(); + role_ = auth_reader.getRole(); + user_or_role_ = auth_reader.getUserOrRole(); + if (auth_reader.hasPassword()) { + const auto password_reader = auth_reader.getPassword(); password_ = dynamic_cast(storage->Load(password_reader, loaded_uids)); - } else { - password_ = nullptr; } - is_create_ = reader.getIsCreate(); + for (const auto &privilege : auth_reader.getPrivileges()) { + switch (privilege) { + case capnp::AuthQuery::Privilege::CREATE: + privileges_.push_back(Privilege::CREATE); + break; + case capnp::AuthQuery::Privilege::DELETE: + privileges_.push_back(Privilege::DELETE); + break; + case capnp::AuthQuery::Privilege::MATCH: + privileges_.push_back(Privilege::MATCH); + break; + case capnp::AuthQuery::Privilege::MERGE: + privileges_.push_back(Privilege::MERGE); + break; + case capnp::AuthQuery::Privilege::SET: + privileges_.push_back(Privilege::SET); + break; + case capnp::AuthQuery::Privilege::AUTH: + privileges_.push_back(Privilege::AUTH); + break; + case capnp::AuthQuery::Privilege::STREAM: + privileges_.push_back(Privilege::STREAM); + break; + } + } } -ModifyUser *ModifyUser::Construct(const capnp::ModifyUser::Reader &reader, - AstStorage *storage) { - return storage->Create(); -} - -// DropUser. -void DropUser::Save(capnp::Clause::Builder *clause_builder, - std::vector *saved_uids) { - Clause::Save(clause_builder, saved_uids); - auto builder = clause_builder->initDropUser(); - DropUser::Save(&builder, saved_uids); -} - -void DropUser::Save(capnp::DropUser::Builder *builder, - std::vector *saved_uids) { - auto usernames_builder = builder->initUsernames(usernames_.size()); - utils::SaveVector(usernames_, &usernames_builder); -} - -void DropUser::Load(const capnp::Tree::Reader &base_reader, AstStorage *storage, - std::vector *loaded_uids) { - Clause::Load(base_reader, storage, loaded_uids); - auto reader = base_reader.getClause().getDropUser(); - usernames_.clear(); - utils::LoadVector(&usernames_, reader.getUsernames()); -} - -DropUser *DropUser::Construct(const capnp::DropUser::Reader &reader, - AstStorage *storage) { - return storage->Create(); +AuthQuery *AuthQuery::Construct(const capnp::AuthQuery::Reader &reader, + AstStorage *storage) { + return storage->Create(); } // CypherUnion diff --git a/src/query/frontend/ast/ast.hpp b/src/query/frontend/ast/ast.hpp index fe3164025..e829def1e 100644 --- a/src/query/frontend/ast/ast.hpp +++ b/src/query/frontend/ast/ast.hpp @@ -2339,68 +2339,67 @@ class CreateIndex : public Clause { std::vector *saved_uids); }; -class ModifyUser : public Clause { +class AuthQuery : public Clause { friend class AstStorage; public: + enum class Action { + CREATE_ROLE, + DROP_ROLE, + SHOW_ROLES, + CREATE_USER, + SET_PASSWORD, + DROP_USER, + SHOW_USERS, + GRANT_ROLE, + REVOKE_ROLE, + GRANT_PRIVILEGE, + DENY_PRIVILEGE, + REVOKE_PRIVILEGE, + SHOW_GRANTS, + SHOW_ROLE_FOR_USER, + SHOW_USERS_FOR_ROLE + }; + + enum class Privilege { CREATE, DELETE, MATCH, MERGE, SET, AUTH, STREAM }; + + Action action_; + std::string user_; + std::string role_; + std::string user_or_role_; + Expression *password_{nullptr}; + std::vector privileges_; + DEFVISITABLE(TreeVisitor); DEFVISITABLE(HierarchicalTreeVisitor); - ModifyUser *Clone(AstStorage &storage) const override { - return storage.Create( - username_, password_ ? password_->Clone(storage) : nullptr, is_create_); + AuthQuery *Clone(AstStorage &storage) const override { + return storage.Create( + action_, user_, role_, user_or_role_, + password_ ? password_->Clone(storage) : nullptr, privileges_); } - static ModifyUser *Construct(const capnp::ModifyUser::Reader &reader, - AstStorage *storage); + static AuthQuery *Construct(const capnp::AuthQuery::Reader &reader, + AstStorage *storage); using Clause::Save; - std::string username_; - Expression *password_; - bool is_create_; - protected: - explicit ModifyUser(int uid) : Clause(uid) {} - ModifyUser(int uid, std::string username, Expression *password, - bool is_create) + explicit AuthQuery(int uid) : Clause(uid) {} + + explicit AuthQuery(int uid, Action action, std::string user, std::string role, + std::string user_or_role, Expression *password, + std::vector privileges) : Clause(uid), - username_(std::move(username)), + action_(action), + user_(user), + role_(role), + user_or_role_(user_or_role), password_(password), - is_create_(is_create) {} + privileges_(privileges) {} void Save(capnp::Clause::Builder *builder, std::vector *saved_uids) override; - virtual void Save(capnp::ModifyUser::Builder *builder, - std::vector *saved_uids); - void Load(const capnp::Tree::Reader &base_reader, AstStorage *storage, - std::vector *loaded_uids) override; -}; - -class DropUser : public Clause { - friend class AstStorage; - - public: - DEFVISITABLE(TreeVisitor); - DEFVISITABLE(HierarchicalTreeVisitor); - - DropUser *Clone(AstStorage &storage) const override { - return storage.Create(usernames_); - } - - static DropUser *Construct(const capnp::DropUser::Reader &reader, - AstStorage *storage); - using Clause::Save; - - std::vector usernames_; - - protected: - explicit DropUser(int uid) : Clause(uid) {} - DropUser(int uid, std::vector usernames) - : Clause(uid), usernames_(usernames) {} - - void Save(capnp::Clause::Builder *builder, - std::vector *saved_uids) override; - virtual void Save(capnp::DropUser::Builder *builder, + virtual void Save(capnp::AuthQuery::Builder *builder, std::vector *saved_uids); void Load(const capnp::Tree::Reader &base_reader, AstStorage *storage, std::vector *loaded_uids) override; diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index 93f2e1ec2..91d1dfac3 100644 --- a/src/query/frontend/ast/ast_visitor.hpp +++ b/src/query/frontend/ast/ast_visitor.hpp @@ -61,8 +61,7 @@ class RemoveLabels; class Merge; class Unwind; class CreateIndex; -class ModifyUser; -class DropUser; +class AuthQuery; class CreateStream; class DropStream; class ShowStreams; @@ -84,9 +83,9 @@ using TreeCompositeVisitor = ::utils::CompositeVisitor< using TreeLeafVisitor = ::utils::LeafVisitor; + CreateIndex, AuthQuery, CreateStream, DropStream, + ShowStreams, StartStopStream, StartStopAllStreams, + TestStream>; class HierarchicalTreeVisitor : public TreeCompositeVisitor, public TreeLeafVisitor { @@ -109,8 +108,7 @@ using TreeVisitor = ::utils::Visitor< Aggregation, Function, Reduce, Extract, All, Single, ParameterLookup, Create, Match, Return, With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty, SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, - Unwind, Identifier, PrimitiveLiteral, CreateIndex, ModifyUser, DropUser, - CreateStream, DropStream, ShowStreams, StartStopStream, StartStopAllStreams, - TestStream>; + Unwind, Identifier, PrimitiveLiteral, CreateIndex, AuthQuery, CreateStream, + DropStream, ShowStreams, StartStopStream, StartStopAllStreams, TestStream>; } // namespace query diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index eb7e113c4..86f3a1e57 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -28,13 +29,10 @@ antlrcpp::Any CypherMainVisitor::visitAuthQuery( MemgraphCypher::AuthQueryContext *ctx) { query_ = storage_.query(); query_->single_query_ = storage_.Create(); - Clause *clause = nullptr; - if (ctx->modifyUser()) { - clause = ctx->modifyUser()->accept(this).as(); - } else if (ctx->dropUser()) { - clause = ctx->dropUser()->accept(this).as(); - } - query_->single_query_->clauses_ = {clause}; + CHECK(ctx->children.size() == 1) + << "AuthQuery should have exactly one child!"; + query_->single_query_->clauses_.push_back( + ctx->children[0]->accept(this).as()); return query_; } @@ -259,46 +257,224 @@ antlrcpp::Any CypherMainVisitor::visitCreateIndex( } /** - * @return ModifyUser* + * @return std::string */ -antlrcpp::Any CypherMainVisitor::visitModifyUser( - MemgraphCypher::ModifyUserContext *ctx) { - std::string username(ctx->userName->getText()); - Expression *password = nullptr; - bool is_create = static_cast(ctx->CREATE()); - for (auto option : ctx->modifyUserOption()) { - if (option->passwordOption()) { - if (password) { - throw QueryException("password should be set at most once"); - } - password = option->passwordOption()->accept(this); - continue; - } - LOG(FATAL) << "Expected to handle all cases above."; +antlrcpp::Any CypherMainVisitor::visitUserOrRoleName( + MemgraphCypher::UserOrRoleNameContext *ctx) { + std::string value = ctx->symbolicName()->accept(this).as(); + const std::regex NAME_REGEX("[a-zA-Z0-9_.+-]+"); + if (!std::regex_match(value, NAME_REGEX)) { + throw SyntaxException("invalid user or role name"); } - return storage_.Create(username, password, is_create); + return value; } /** - * @return Expression* + * @return AuthQuery* */ -antlrcpp::Any CypherMainVisitor::visitPasswordOption( - MemgraphCypher::PasswordOptionContext *ctx) { - if (!ctx->literal()->StringLiteral() && !ctx->literal()->CYPHERNULL()) { +antlrcpp::Any CypherMainVisitor::visitCreateRole( + MemgraphCypher::CreateRoleContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::CREATE_ROLE; + auth->role_ = ctx->role->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitDropRole( + MemgraphCypher::DropRoleContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::DROP_ROLE; + auth->role_ = ctx->role->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitShowRoles( + MemgraphCypher::ShowRolesContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::SHOW_ROLES; + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitCreateUser( + MemgraphCypher::CreateUserContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::CREATE_USER; + auth->user_ = ctx->user->accept(this).as(); + if (ctx->password) { + if (!ctx->password->StringLiteral() && !ctx->literal()->CYPHERNULL()) { + throw SyntaxException("password should be a string literal or NULL"); + } + auth->password_ = ctx->password->accept(this); + } + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitSetPassword( + MemgraphCypher::SetPasswordContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::SET_PASSWORD; + auth->user_ = ctx->user->accept(this).as(); + if (!ctx->password->StringLiteral() && !ctx->literal()->CYPHERNULL()) { throw SyntaxException("password should be a string literal or NULL"); } - return ctx->literal()->accept(this); + auth->password_ = ctx->password->accept(this); + return auth; } /** - * @return DropUser* + * @return AuthQuery* */ antlrcpp::Any CypherMainVisitor::visitDropUser( MemgraphCypher::DropUserContext *ctx) { - std::vector usernames; - for (auto username_ptr : ctx->userName) - usernames.emplace_back(username_ptr->getText()); - return storage_.Create(usernames); + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::DROP_USER; + auth->user_ = ctx->user->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitShowUsers( + MemgraphCypher::ShowUsersContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::SHOW_USERS; + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitGrantRole( + MemgraphCypher::GrantRoleContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::GRANT_ROLE; + auth->role_ = ctx->role->accept(this).as(); + auth->user_ = ctx->user->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitRevokeRole( + MemgraphCypher::RevokeRoleContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::REVOKE_ROLE; + auth->role_ = ctx->role->accept(this).as(); + auth->user_ = ctx->user->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitGrantPrivilege( + MemgraphCypher::GrantPrivilegeContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::GRANT_PRIVILEGE; + auth->user_or_role_ = ctx->userOrRole->accept(this).as(); + for (auto *privilege : ctx->privilegeList()->privilege()) { + auth->privileges_.push_back(privilege->accept(this)); + } + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitDenyPrivilege( + MemgraphCypher::DenyPrivilegeContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::DENY_PRIVILEGE; + auth->user_or_role_ = ctx->userOrRole->accept(this).as(); + for (auto *privilege : ctx->privilegeList()->privilege()) { + auth->privileges_.push_back(privilege->accept(this)); + } + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitRevokePrivilege( + MemgraphCypher::RevokePrivilegeContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::REVOKE_PRIVILEGE; + auth->user_or_role_ = ctx->userOrRole->accept(this).as(); + if (ctx->privilegeList()) { + for (auto *privilege : ctx->privilegeList()->privilege()) { + auth->privileges_.push_back(privilege->accept(this)); + } + } else { + /* revoke all privileges */ + auth->privileges_ = { + AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE, + AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE, + AuthQuery::Privilege::SET, AuthQuery::Privilege::AUTH, + AuthQuery::Privilege::STREAM}; + } + return auth; +} + +/** + * @return AuthQuery::Privilege + */ +antlrcpp::Any CypherMainVisitor::visitPrivilege( + MemgraphCypher::PrivilegeContext *ctx) { + if (ctx->CREATE()) return AuthQuery::Privilege::CREATE; + if (ctx->DELETE()) return AuthQuery::Privilege::DELETE; + if (ctx->MATCH()) return AuthQuery::Privilege::MATCH; + if (ctx->MERGE()) return AuthQuery::Privilege::MERGE; + if (ctx->SET()) return AuthQuery::Privilege::SET; + if (ctx->AUTH()) return AuthQuery::Privilege::AUTH; + if (ctx->STREAM()) return AuthQuery::Privilege::STREAM; + LOG(FATAL) << "Should not get here - unknown privilege!"; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitShowGrants( + MemgraphCypher::ShowGrantsContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::SHOW_GRANTS; + auth->user_or_role_ = ctx->userOrRole->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitShowRoleForUser( + MemgraphCypher::ShowRoleForUserContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::SHOW_ROLE_FOR_USER; + auth->user_ = ctx->user->accept(this).as(); + return auth; +} + +/** + * @return AuthQuery* + */ +antlrcpp::Any CypherMainVisitor::visitShowUsersForRole( + MemgraphCypher::ShowUsersForRoleContext *ctx) { + AuthQuery *auth = storage_.Create(); + auth->action_ = AuthQuery::Action::SHOW_USERS_FOR_ROLE; + auth->role_ = ctx->role->accept(this).as(); + return auth; } /** diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp index bc78c8f7e..d84b166b0 100644 --- a/src/query/frontend/ast/cypher_main_visitor.hpp +++ b/src/query/frontend/ast/cypher_main_visitor.hpp @@ -177,6 +177,28 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { */ antlrcpp::Any visitCreate(MemgraphCypher::CreateContext *ctx) override; + /** + * @return std::string + */ + antlrcpp::Any visitUserOrRoleName( + MemgraphCypher::UserOrRoleNameContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitCreateRole( + MemgraphCypher::CreateRoleContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitDropRole(MemgraphCypher::DropRoleContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitShowRoles(MemgraphCypher::ShowRolesContext *ctx) override; + /** * @return CreateIndex* */ @@ -184,19 +206,79 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor { MemgraphCypher::CreateIndexContext *ctx) override; /** - * @return ModifyUser* + * @return AuthQuery* */ - antlrcpp::Any visitModifyUser( - MemgraphCypher::ModifyUserContext *ctx) override; - - antlrcpp::Any visitPasswordOption( - MemgraphCypher::PasswordOptionContext *ctx) override; + antlrcpp::Any visitCreateUser( + MemgraphCypher::CreateUserContext *ctx) override; /** - * @return DropUser* + * @return AuthQuery* + */ + antlrcpp::Any visitSetPassword( + MemgraphCypher::SetPasswordContext *ctx) override; + + /** + * @return AuthQuery* */ antlrcpp::Any visitDropUser(MemgraphCypher::DropUserContext *ctx) override; + /** + * @return AuthQuery* + */ + antlrcpp::Any visitShowUsers(MemgraphCypher::ShowUsersContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitGrantRole(MemgraphCypher::GrantRoleContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitRevokeRole( + MemgraphCypher::RevokeRoleContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitGrantPrivilege( + MemgraphCypher::GrantPrivilegeContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitDenyPrivilege( + MemgraphCypher::DenyPrivilegeContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitRevokePrivilege( + MemgraphCypher::RevokePrivilegeContext *ctx) override; + + /** + * @return AuthQuery::Privilege + */ + antlrcpp::Any visitPrivilege(MemgraphCypher::PrivilegeContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitShowGrants( + MemgraphCypher::ShowGrantsContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitShowRoleForUser( + MemgraphCypher::ShowRoleForUserContext *ctx) override; + + /** + * @return AuthQuery* + */ + antlrcpp::Any visitShowUsersForRole( + MemgraphCypher::ShowUsersForRoleContext *ctx) override; + /** * @return CreateStream* */ diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index e4cc22816..56c533074 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -8,23 +8,36 @@ import Cypher ; memgraphCypherKeyword : cypherKeyword | ALTER + | AUTH | BATCH | BATCHES | DATA + | DENY | DROP + | FOR + | FROM + | GRANT + | GRANTS + | IDENTIFIED | INTERVAL | K_TEST | KAFKA | LOAD | PASSWORD + | PRIVILEGES + | REVOKE + | ROLE + | ROLES | SIZE | START | STOP | STREAM | STREAMS + | TO | TOPIC | TRANSFORM | USER + | USERS ; symbolicName : UnescapedSymbolicName @@ -37,19 +50,60 @@ query : regularQuery | streamQuery ; -authQuery : modifyUser +authQuery : createRole + | dropRole + | showRoles + | createUser + | setPassword | dropUser + | showUsers + | grantRole + | revokeRole + | grantPrivilege + | denyPrivilege + | revokePrivilege + | showGrants + | showRoleForUser + | showUsersForRole ; -modifyUser : ( CREATE | ALTER ) USER userName=UnescapedSymbolicName - ( WITH ( modifyUserOption )+ )? ; +userOrRoleName : symbolicName ; -modifyUserOption : passwordOption ; +createRole : CREATE ROLE role=userOrRoleName ; -passwordOption : PASSWORD literal; +dropRole : DROP ROLE role=userOrRoleName ; -dropUser : DROP USER userName+=UnescapedSymbolicName - ( ',' userName+=UnescapedSymbolicName )* ; +showRoles : SHOW ROLES ; + +createUser : CREATE USER user=userOrRoleName + ( IDENTIFIED BY password=literal )? ; + +setPassword : SET PASSWORD FOR user=userOrRoleName TO password=literal; + +dropUser : DROP USER user=userOrRoleName ; + +showUsers : SHOW USERS ; + +grantRole : GRANT ROLE role=userOrRoleName TO user=userOrRoleName ; + +revokeRole : REVOKE ROLE role=userOrRoleName FROM user=userOrRoleName ; + +grantPrivilege : GRANT privilegeList TO userOrRole=userOrRoleName ; + +denyPrivilege : DENY privilegeList TO userOrRole=userOrRoleName ; + +revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ; + +privilege : CREATE | DELETE | MATCH | MERGE | SET + | AUTH | STREAM ; + +privilegeList : privilege ( ',' privilege )* ; + +showGrants : SHOW GRANTS FOR userOrRole=userOrRoleName ; + +showRoleForUser : SHOW ROLE FOR USER user=userOrRoleName ; + +showUsersForRole : SHOW USERS FOR ROLE role=userOrRoleName ; streamQuery : createStream | dropStream diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 index e1b3a6bf8..0ec3a28a7 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 @@ -11,20 +11,33 @@ lexer grammar MemgraphCypherLexer ; import CypherLexer ; ALTER : A L T E R ; +AUTH : A U T H ; BATCH : B A T C H ; BATCHES : B A T C H E S ; DATA : D A T A ; +DENY : D E N Y ; DROP : D R O P ; +FOR : F O R ; +FROM : F R O M ; +GRANT : G R A N T ; +GRANTS : G R A N T S ; +IDENTIFIED : I D E N T I F I E D ; INTERVAL : I N T E R V A L ; K_TEST : T E S T ; KAFKA : K A F K A ; LOAD : L O A D ; PASSWORD : P A S S W O R D ; +PRIVILEGES : P R I V I L E G E S ; +REVOKE : R E V O K E ; +ROLE : R O L E ; +ROLES : R O L E S ; SIZE : S I Z E ; START : S T A R T ; STOP : S T O P ; STREAM : S T R E A M ; STREAMS : S T R E A M S ; +TO : T O ; TOPIC : T O P I C ; TRANSFORM : T R A N S F O R M ; USER : U S E R ; +USERS : U S E R S ; diff --git a/src/query/frontend/semantic/symbol_generator.cpp b/src/query/frontend/semantic/symbol_generator.cpp index b3b66de67..0d6ea58d7 100644 --- a/src/query/frontend/semantic/symbol_generator.cpp +++ b/src/query/frontend/semantic/symbol_generator.cpp @@ -220,9 +220,7 @@ bool SymbolGenerator::PostVisit(Match &) { bool SymbolGenerator::Visit(CreateIndex &) { return true; } -bool SymbolGenerator::Visit(ModifyUser &) { return true; } - -bool SymbolGenerator::Visit(DropUser &) { return true; } +bool SymbolGenerator::Visit(AuthQuery &) { return true; } bool SymbolGenerator::Visit(CreateStream &) { return true; } diff --git a/src/query/frontend/semantic/symbol_generator.hpp b/src/query/frontend/semantic/symbol_generator.hpp index 5b8c753a7..afbbfd5eb 100644 --- a/src/query/frontend/semantic/symbol_generator.hpp +++ b/src/query/frontend/semantic/symbol_generator.hpp @@ -47,8 +47,7 @@ class SymbolGenerator : public HierarchicalTreeVisitor { bool PreVisit(Match &) override; bool PostVisit(Match &) override; bool Visit(CreateIndex &) override; - bool Visit(ModifyUser &) override; - bool Visit(DropUser &) override; + bool Visit(AuthQuery &) override; bool Visit(CreateStream &) override; bool Visit(DropStream &) override; bool Visit(ShowStreams &) override; diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp index 0aeb26ca7..075050e59 100644 --- a/src/query/interpret/eval.hpp +++ b/src/query/interpret/eval.hpp @@ -49,8 +49,7 @@ class ExpressionEvaluator : public TreeVisitor { BLOCK_VISIT(Merge); BLOCK_VISIT(Unwind); BLOCK_VISIT(CreateIndex); - BLOCK_VISIT(ModifyUser); - BLOCK_VISIT(DropUser); + BLOCK_VISIT(AuthQuery); BLOCK_VISIT(CreateStream); BLOCK_VISIT(DropStream); BLOCK_VISIT(ShowStreams); diff --git a/src/query/plan/cost_estimator.hpp b/src/query/plan/cost_estimator.hpp index 0bd18de31..dd8afdff6 100644 --- a/src/query/plan/cost_estimator.hpp +++ b/src/query/plan/cost_estimator.hpp @@ -185,8 +185,7 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor { bool Visit(Once &) override { return true; } bool Visit(CreateIndex &) override { return true; } - bool Visit(ModifyUser &) override { return true; } - bool Visit(DropUser &) override { return true; } + bool Visit(AuthHandler &) override { return true; } bool Visit(CreateStream &) override { return true; } bool Visit(DropStream &) override { return true; } bool Visit(ShowStreams &) override { return true; } diff --git a/src/query/plan/distributed.cpp b/src/query/plan/distributed.cpp index e0e1f3129..96d513afa 100644 --- a/src/query/plan/distributed.cpp +++ b/src/query/plan/distributed.cpp @@ -87,8 +87,7 @@ class IndependentSubtreeFinder : public HierarchicalLogicalOperatorVisitor { // These don't use any symbols bool Visit(Once &) override { return true; } bool Visit(CreateIndex &) override { return true; } - bool Visit(ModifyUser &) override { return true; } - bool Visit(DropUser &) override { return true; } + bool Visit(AuthHandler &) override { return true; } bool Visit(CreateStream &) override { return true; } bool Visit(DropStream &) override { return true; } bool Visit(ShowStreams &) override { return true; } @@ -1344,9 +1343,7 @@ class DistributedPlanner : public HierarchicalLogicalOperatorVisitor { bool Visit(CreateIndex &) override { return true; } - bool Visit(ModifyUser &) override { return true; } - - bool Visit(DropUser &) override { return true; } + bool Visit(AuthHandler &) override { return true; } bool Visit(CreateStream &) override { return true; } diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 11c029b6c..ba26ca500 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -159,7 +159,7 @@ VertexAccessor &CreateVertexOnWorker(int worker_id, NodeAtom *node_atom, int current_worker_id = 0; // TODO: Figure out a better solution. if (auto *distributed_db = - dynamic_cast(&dba.db())) { + dynamic_cast(&dba.db())) { current_worker_id = distributed_db->WorkerId(); } else { CHECK(dynamic_cast(&dba.db())); @@ -1392,7 +1392,8 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor { // For the given (edge, vertex, weight, depth) tuple checks if they // satisfy the "where" condition. if so, places them in the priority queue. auto expand_pair = [this, &evaluator, &frame, &create_state]( - EdgeAccessor edge, VertexAccessor vertex, double weight, int depth) { + EdgeAccessor edge, VertexAccessor vertex, + double weight, int depth) { SwitchAccessor(edge, self_.graph_view_); SwitchAccessor(vertex, self_.graph_view_); @@ -3889,130 +3890,120 @@ std::unique_ptr PullRemoteOrderBy::MakeCursor( return std::make_unique(*this, db); } -ModifyUser::ModifyUser(std::string username, Expression *password, - bool is_create) - : username_(std::move(username)), +AuthHandler::AuthHandler(AuthQuery::Action action, std::string user, + std::string role, std::string user_or_role, + Expression *password, + std::vector privileges) + : action_(action), + user_(user), + role_(role), + user_or_role_(user_or_role), password_(password), - is_create_(is_create) {} + privileges_(privileges) {} -bool ModifyUser::Accept(HierarchicalLogicalOperatorVisitor &visitor) { +bool AuthHandler::Accept(HierarchicalLogicalOperatorVisitor &visitor) { return visitor.Visit(*this); } -WITHOUT_SINGLE_INPUT(ModifyUser) - -class ModifyUserCursor : public Cursor { +class AuthHandlerCursor : public Cursor { public: - ModifyUserCursor(const ModifyUser &self) : self_(self) {} + AuthHandlerCursor(const AuthHandler &self) : self_(self) {} bool Pull(Frame &frame, Context &ctx) override { if (ctx.in_explicit_transaction_) { throw UserModificationInMulticommandTxException(); } + ExpressionEvaluator evaluator(frame, &ctx, GraphView::OLD); - - TypedValue password_tv = self_.password()->Accept(evaluator); - if (password_tv.type() != TypedValue::Type::String) { - throw QueryRuntimeException(fmt::format( - "Password must be a string, not '{}'", password_tv.type())); - } - - // All of the following operations are done with a lock. - std::lock_guard guard(ctx.auth_->WithLock()); - - std::experimental::optional user; - if (self_.is_create()) { - // Create a new user. - user = ctx.auth_->AddUser(self_.username()); - if (!user) { - throw QueryRuntimeException( - fmt::format("User '{}' already exists!", self_.username())); + std::experimental::optional password; + /* TODO(mferencevic): handle null passwords properly */ + if (self_.password()) { + auto password_tv = self_.password()->Accept(evaluator); + if (!password_tv.IsString()) { + throw QueryRuntimeException("Password must be a string, not '{}'!", + password_tv.type()); } - } else { - // Update an existing user. - user = ctx.auth_->GetUser(self_.username()); - if (!user) { - throw QueryRuntimeException( - fmt::format("User '{}' doesn't exist!", self_.username())); + password = password_tv.ValueString(); + } + + auto &auth = *ctx.auth_; + std::lock_guard lock(auth.WithLock()); + + switch (self_.action()) { + case AuthQuery::Action::CREATE_USER: { + if (!password) { + throw QueryRuntimeException( + "Password must be provided when creating a user!"); + } + auto user = auth.AddUser(self_.user()); + if (!user) { + throw QueryRuntimeException("User '{}' already exists!", + self_.user()); + } + user->UpdatePassword(*password); + if (!auth.SaveUser(*user)) { + throw QueryRuntimeException("Couldn't save user '{}'!", self_.user()); + } + break; } + case AuthQuery::Action::DROP_USER: { + auto user = auth.GetUser(self_.user()); + if (!user) { + throw QueryRuntimeException("User '{}' doesn't exist!", self_.user()); + } + if (!auth.RemoveUser(self_.user())) { + throw QueryRuntimeException("Couldn't remove user '{}'!", + self_.user()); + } + break; + } + case AuthQuery::Action::SET_PASSWORD: { + if (!password) { + throw QueryRuntimeException("Password must be provided!"); + } + auto user = auth.GetUser(self_.user()); + if (!user) { + throw QueryRuntimeException("User '{}' doesn't exist!", self_.user()); + } + user->UpdatePassword(*password); + if (!auth.SaveUser(*user)) { + throw QueryRuntimeException("Couldn't set password for user '{}'!", + self_.user()); + } + break; + } + case AuthQuery::Action::CREATE_ROLE: + case AuthQuery::Action::DROP_ROLE: + case AuthQuery::Action::SHOW_ROLES: + case AuthQuery::Action::SHOW_USERS: + case AuthQuery::Action::GRANT_ROLE: + case AuthQuery::Action::REVOKE_ROLE: + case AuthQuery::Action::GRANT_PRIVILEGE: + case AuthQuery::Action::DENY_PRIVILEGE: + case AuthQuery::Action::REVOKE_PRIVILEGE: + case AuthQuery::Action::SHOW_GRANTS: + case AuthQuery::Action::SHOW_ROLE_FOR_USER: + case AuthQuery::Action::SHOW_USERS_FOR_ROLE: + throw utils::NotYetImplemented("user auth"); } - - // Set the password and save the user. - user->UpdatePassword(password_tv.Value()); - if (!ctx.auth_->SaveUser(*user)) { - throw QueryRuntimeException( - fmt::format("Couldn't save user '{}'!", self_.username())); - } - return false; } - void Reset() override {} - - private: - const ModifyUser &self_; -}; - -std::unique_ptr ModifyUser::MakeCursor( - database::GraphDbAccessor &) const { - return std::make_unique(*this); -} - -bool DropUser::Accept(HierarchicalLogicalOperatorVisitor &visitor) { - return visitor.Visit(*this); -} - -WITHOUT_SINGLE_INPUT(DropUser) - -class DropUserCursor : public Cursor { - public: - DropUserCursor(const DropUser &self) : self_(self) {} - - bool Pull(Frame &, Context &ctx) override { - if (ctx.in_explicit_transaction_) { - throw UserModificationInMulticommandTxException(); - } - - // All of the following operations are done with a lock. - std::lock_guard guard(ctx.auth_->WithLock()); - - // Check if all users exist. - for (const auto &username : self_.usernames()) { - auto user = ctx.auth_->GetUser(username); - if (!user) { - throw QueryRuntimeException( - fmt::format("User '{}' doesn't exist!", username)); - } - } - - // Delete all users. - std::vector failed; - for (const auto &username : self_.usernames()) { - if (!ctx.auth_->RemoveUser(username)) { - failed.push_back(username); - } - } - - // Check for failures. - if (failed.size() > 0) { - throw QueryRuntimeException(fmt::format("Couldn't remove users: '{}'!", - utils::Join(failed, "', '"))); - } - - return false; + void Reset() override { + LOG(FATAL) << "AuthHandler cursor should never be reset"; } - void Reset() override {} - private: - const DropUser &self_; + const AuthHandler &self_; }; -std::unique_ptr DropUser::MakeCursor( - database::GraphDbAccessor &) const { - return std::make_unique(*this); +std::unique_ptr AuthHandler::MakeCursor( + database::GraphDbAccessor &db) const { + return std::make_unique(*this); } +WITHOUT_SINGLE_INPUT(AuthHandler) + CreateStream::CreateStream(std::string stream_name, Expression *stream_uri, Expression *stream_topic, Expression *transform_uri, Expression *batch_interval_in_ms, diff --git a/src/query/plan/operator.lcp b/src/query/plan/operator.lcp index e4cf8d4f9..05e5a9f42 100644 --- a/src/query/plan/operator.lcp +++ b/src/query/plan/operator.lcp @@ -102,8 +102,7 @@ class PullRemote; class Synchronize; class Cartesian; class PullRemoteOrderBy; -class ModifyUser; -class DropUser; +class AuthHandler; class CreateStream; class DropStream; class ShowStreams; @@ -122,9 +121,9 @@ using LogicalOperatorCompositeVisitor = ::utils::CompositeVisitor< Cartesian, PullRemoteOrderBy>; using LogicalOperatorLeafVisitor = - ::utils::LeafVisitor; + ::utils::LeafVisitor; /** * @brief Base class for hierarhical visitors of @c LogicalOperator class @@ -1969,6 +1968,114 @@ and returns true, once.") cpp<#) (:serialize :capnp)) +(lcp:define-class auth-handler (logical-operator) + ((action "AuthQuery::Action" :reader t + :capnp-init nil + :capnp-type "Ast.AuthQuery.Action" + :capnp-save (lcp:capnp-save-enum "::query::capnp::AuthQuery::Action" + "AuthQuery::Action" + '(create-role drop-role show-roles + create-user set-password + drop-user show-users grant-role + revoke-role grant-privilege + deny-privilege revoke-privilege + show-grants show-role-for-user + show-users-for-role)) + :capnp-load (lcp:capnp-load-enum "::query::capnp::AuthQuery::Action" + "AuthQuery::Action" + '(create-role drop-role show-roles + create-user set-password + drop-user show-users grant-role + revoke-role grant-privilege + deny-privilege revoke-privilege + show-grants show-role-for-user + show-users-for-role))) + (user "std::string" :reader t) + (role "std::string" :reader t) + (user-or-role "std::string" :reader t) + (password "Expression *" :reader t + :capnp-type "Ast.Tree" :capnp-init nil + :capnp-save #'save-ast-pointer + :capnp-load (load-ast-pointer "Expression *")) + (privileges "std::vector" :reader t + :capnp-type "List(Ast.AuthQuery.Privilege)" + :capnp-save + (lambda (builder member-name) + #>cpp + for (size_t i = 0; i < ${member-name}.size(); ++i) { + switch (privileges_[i]) { + case AuthQuery::Privilege::CREATE: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::CREATE); + break; + case AuthQuery::Privilege::DELETE: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::DELETE); + break; + case AuthQuery::Privilege::MATCH: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::MATCH); + break; + case AuthQuery::Privilege::MERGE: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::MERGE); + break; + case AuthQuery::Privilege::SET: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::SET); + break; + case AuthQuery::Privilege::AUTH: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::AUTH); + break; + case AuthQuery::Privilege::STREAM: + ${builder}.set(i, query::capnp::AuthQuery::Privilege::STREAM); + break; + } + } + cpp<#) + :capnp-load + (lambda (reader member-name) + #>cpp + for (auto privilege : ${reader}) { + switch (privilege) { + case query::capnp::AuthQuery::Privilege::CREATE: + ${member-name}.push_back(AuthQuery::Privilege::CREATE); + break; + case query::capnp::AuthQuery::Privilege::DELETE: + ${member-name}.push_back(AuthQuery::Privilege::DELETE); + break; + case query::capnp::AuthQuery::Privilege::MATCH: + ${member-name}.push_back(AuthQuery::Privilege::MATCH); + break; + case query::capnp::AuthQuery::Privilege::MERGE: + ${member-name}.push_back(AuthQuery::Privilege::MERGE); + break; + case query::capnp::AuthQuery::Privilege::SET: + ${member-name}.push_back(AuthQuery::Privilege::SET); + break; + case query::capnp::AuthQuery::Privilege::AUTH: + ${member-name}.push_back(AuthQuery::Privilege::AUTH); + break; + case query::capnp::AuthQuery::Privilege::STREAM: + ${member-name}.push_back(AuthQuery::Privilege::STREAM); + break; + } + } + cpp<#))) + (:public + #>cpp + AuthHandler(AuthQuery::Action action, std::string user, std::string role, + std::string user_or_role, Expression * password, + std::vector privileges); + + bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; + std::unique_ptr MakeCursor(database::GraphDbAccessor & db) + const override; + virtual std::vector ModifiedSymbols(const SymbolTable &) + const override { return {}; } + + bool HasSingleInput() const override; + std::shared_ptr input() const override; + void set_input(std::shared_ptr) override; + cpp<#) + (:protected #>cpp AuthHandler() {} cpp<#) + (:serialize :capnp)) + (lcp:define-class unwind (logical-operator) ((input "std::shared_ptr" :capnp-save #'save-operator-pointer @@ -1991,8 +2098,10 @@ Input is optional (unwind can be the first clause in a query).") database::GraphDbAccessor &db) const override; std::vector ModifiedSymbols(const SymbolTable &) const override; - bool HasSingleInput() const override { return true; } - std::shared_ptr input() const override { return input_; } + bool HasSingleInput() const override { + return true; } + std::shared_ptr input() const override { + return input_; } void set_input(std::shared_ptr input) override { input_ = input; } @@ -2338,70 +2447,6 @@ by having only one result from each worker.") (:private #>cpp PullRemoteOrderBy() {} cpp<#) (:serialize :capnp)) -(lcp:define-class modify-user (logical-operator) - ((input "std::shared_ptr" - :capnp-save #'save-operator-pointer - :capnp-load #'load-operator-pointer) - (username "std::string" :reader t) - (password "Expression *" :reader t - :capnp-type "Ast.Tree" - :capnp-init nil - :capnp-save #'save-ast-pointer - :capnp-load (load-ast-pointer "Expression *")) - (is-create :bool :reader t)) - (:documentation - "Operator that creates a new database user or modifies an existing one.") - (:public - #>cpp - ModifyUser(std::string username, Expression *password, bool is_create); - - bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; - std::unique_ptr MakeCursor( - database::GraphDbAccessor &db) const override; - - std::vector ModifiedSymbols(const SymbolTable &) const override { - return std::vector(); - } - - bool HasSingleInput() const override; - std::shared_ptr input() const override; - void set_input(std::shared_ptr input) override; - cpp<#) - (:private - #>cpp - ModifyUser() {} - cpp<#) - (:serialize :capnp)) - -(lcp:define-class drop-user (logical-operator) - ((input "std::shared_ptr" - :capnp-save #'save-operator-pointer - :capnp-load #'load-operator-pointer) - (usernames "std::vector" :reader t)) - (:documentation - "Operator that deletes one or more existing database users.") - (:public - #>cpp - DropUser(std::vector usernames): usernames_(usernames) {} - - bool Accept(HierarchicalLogicalOperatorVisitor &visitor) override; - std::unique_ptr MakeCursor( - database::GraphDbAccessor &db) const override; - - std::vector ModifiedSymbols(const SymbolTable &) const override { - return std::vector(); - } - - bool HasSingleInput() const override; - std::shared_ptr input() const override; - void set_input(std::shared_ptr input) override; - cpp<#) - (:private - #>cpp - DropUser() {} - cpp<#) - (:serialize :capnp)) - (lcp:define-class create-stream (logical-operator) ((stream-name "std::string" :reader t) (stream-uri "Expression *" diff --git a/src/query/plan/preprocess.hpp b/src/query/plan/preprocess.hpp index 93fd65ec8..0a979a03f 100644 --- a/src/query/plan/preprocess.hpp +++ b/src/query/plan/preprocess.hpp @@ -53,8 +53,7 @@ class UsedSymbolsCollector : public HierarchicalTreeVisitor { bool Visit(PrimitiveLiteral &) override { return true; } bool Visit(ParameterLookup &) override { return true; } bool Visit(query::CreateIndex &) override { return true; } - bool Visit(query::ModifyUser &) override { return true; } - bool Visit(query::DropUser &) override { return true; } + bool Visit(query::AuthQuery &) override { return true; } bool Visit(query::CreateStream &) override { return true; } bool Visit(query::DropStream &) override { return true; } bool Visit(query::ShowStreams &) override { return true; } diff --git a/src/query/plan/rule_based_planner.cpp b/src/query/plan/rule_based_planner.cpp index 60cec2359..a2c4e2330 100644 --- a/src/query/plan/rule_based_planner.cpp +++ b/src/query/plan/rule_based_planner.cpp @@ -402,12 +402,7 @@ class ReturnBodyContext : public HierarchicalTreeVisitor { return true; } - bool Visit(query::ModifyUser &) override { - has_aggregation_.emplace_back(false); - return true; - } - - bool Visit(query::DropUser &) override { + bool Visit(query::AuthQuery &) override { has_aggregation_.emplace_back(false); return true; } diff --git a/src/query/plan/rule_based_planner.hpp b/src/query/plan/rule_based_planner.hpp index 65a47a668..50bf99883 100644 --- a/src/query/plan/rule_based_planner.hpp +++ b/src/query/plan/rule_based_planner.hpp @@ -182,15 +182,13 @@ class RuleBasedPlanner { DCHECK(!input_op) << "Unexpected operator before CreateIndex"; input_op = std::make_unique( create_index->label_, create_index->property_); - } else if (auto *modify_user = - dynamic_cast(clause)) { - DCHECK(!input_op) << "Unexpected operator before ModifyUser"; - input_op = std::make_unique( - modify_user->username_, modify_user->password_, - modify_user->is_create_); - } else if (auto *drop_user = dynamic_cast(clause)) { - DCHECK(!input_op) << "Unexpected operator before DropUser"; - input_op = std::make_unique(drop_user->usernames_); + } else if (auto *auth_query = + dynamic_cast(clause)) { + DCHECK(!input_op) << "Unexpected operator before AuthQuery"; + input_op = std::make_unique( + auth_query->action_, auth_query->user_, auth_query->role_, + auth_query->user_or_role_, auth_query->password_, + auth_query->privileges_); } else if (auto *create_stream = dynamic_cast(clause)) { DCHECK(!input_op) << "Unexpected operator before CreateStream"; diff --git a/tests/manual/query_planner.cpp b/tests/manual/query_planner.cpp index 855a242e6..aa22fff1e 100644 --- a/tests/manual/query_planner.cpp +++ b/tests/manual/query_planner.cpp @@ -514,13 +514,8 @@ class PlanPrinter : public query::plan::HierarchicalLogicalOperatorVisitor { return true; } - bool Visit(query::plan::ModifyUser &op) override { - WithPrintLn([](auto &out) { out << "* ModifyUser "; }); - return true; - } - - bool Visit(query::plan::DropUser &op) override { - WithPrintLn([](auto &out) { out << "* DropUser"; }); + bool Visit(query::plan::AuthHandler &op) override { + WithPrintLn([](auto &out) { out << "* AuthHandler"; }); return true; } diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index 269e17acf..01061d084 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -1866,72 +1866,203 @@ TYPED_TEST(CypherMainVisitorTest, UnionAll) { ASSERT_FALSE(return_clause->body_.distinct); } -TYPED_TEST(CypherMainVisitorTest, ModifyUser) { - auto check_modify_user = [](std::string input, std::string username, - std::experimental::optional password, - bool is_create) { - TypeParam ast_generator(input); - auto *query = ast_generator.query_; - ASSERT_TRUE(query->single_query_); - auto *single_query = query->single_query_; - ASSERT_EQ(single_query->clauses_.size(), 1U); - auto *create_user = dynamic_cast(single_query->clauses_[0]); - ASSERT_TRUE(create_user); - EXPECT_EQ(create_user->username_, username); - if (password) { - ASSERT_NE(create_user->password_, nullptr); - CheckLiteral(ast_generator.context_, create_user->password_, *password); - } else { - EXPECT_EQ(create_user->password_, nullptr); - } - EXPECT_EQ(create_user->is_create_, is_create); - }; +template +void check_auth_query(std::string input, AuthQuery::Action action, + std::string user, std::string role, + std::string user_or_role, + std::experimental::optional password, + std::vector privileges) { + AstGeneratorT ast_generator(input); + auto *query = ast_generator.query_; + ASSERT_TRUE(query->single_query_ && + query->single_query_->clauses_.size() == 1U); + auto auth_query = + dynamic_cast(query->single_query_->clauses_[0]); + EXPECT_EQ(auth_query->action_, action); + EXPECT_EQ(auth_query->user_, user); + EXPECT_EQ(auth_query->role_, role); + EXPECT_EQ(auth_query->user_or_role_, user_or_role); + ASSERT_EQ(static_cast(auth_query->password_), + static_cast(password)); + if (password) { + CheckLiteral(ast_generator.context_, auth_query->password_, *password); + } + EXPECT_EQ(auth_query->privileges_, privileges); +} - check_modify_user("CreaTE UsEr dominik", "dominik", - std::experimental::nullopt, true); - check_modify_user("CreaTE UsEr dominik WIth PaSSWORD 'spomenik'", "dominik", - "spomenik", true); - check_modify_user("CreaTE UsEr dominik WIth PaSSWORD NULL", "dominik", - TypedValue::Null, true); - check_modify_user("AlTeR UsEr dominik", "dominik", std::experimental::nullopt, - false); - check_modify_user("ALtEr UsEr dominik", "dominik", std::experimental::nullopt, - false); - check_modify_user("ALtEr UsEr dominik WIth PaSSWORD 'spomenik'", "dominik", - "spomenik", false); - check_modify_user("ALtEr UsEr dominik WIth PaSSWORD NULL", "dominik", - TypedValue::Null, false); - EXPECT_THROW( - check_modify_user( - "CreaTE UsEr dominik WIth PaSSWORD 'spomenik' PaSSwoRD 'u muzeju'", - "dominik", "spomenik", true), - QueryException); - EXPECT_THROW(check_modify_user("CreaTE UsEr dominik WIth PaSSWORD 12345", - "dominik", "spomenik", true), +TYPED_TEST(CypherMainVisitorTest, UserOrRoleName) { + ASSERT_THROW(TypeParam("CREATE ROLE `us|er`"), SyntaxException); + ASSERT_THROW(TypeParam("CREATE ROLE `us er`"), SyntaxException); + check_auth_query("CREATE ROLE `user`", + AuthQuery::Action::CREATE_ROLE, "", "user", "", + {}, {}); + check_auth_query("CREATE ROLE us___er", + AuthQuery::Action::CREATE_ROLE, "", "us___er", "", + {}, {}); + check_auth_query("CREATE ROLE `us+er`", + AuthQuery::Action::CREATE_ROLE, "", "us+er", "", + {}, {}); +} + +TYPED_TEST(CypherMainVisitorTest, CreateRole) { + ASSERT_THROW(TypeParam("CREATE ROLE"), SyntaxException); + check_auth_query("CREATE ROLE rola", + AuthQuery::Action::CREATE_ROLE, "", "rola", "", + {}, {}); + ASSERT_THROW(TypeParam("CREATE ROLE lagano rolamo"), SyntaxException); +} + +TYPED_TEST(CypherMainVisitorTest, DropRole) { + ASSERT_THROW(TypeParam("DROP ROLE"), SyntaxException); + check_auth_query("DROP ROLE rola", AuthQuery::Action::DROP_ROLE, + "", "rola", "", {}, {}); + ASSERT_THROW(TypeParam("DROP ROLE lagano rolamo"), SyntaxException); +} + +TYPED_TEST(CypherMainVisitorTest, ShowRoles) { + ASSERT_THROW(TypeParam("SHOW ROLES ROLES"), SyntaxException); + check_auth_query("SHOW ROLES", AuthQuery::Action::SHOW_ROLES, "", + "", "", {}, {}); +} + +TYPED_TEST(CypherMainVisitorTest, CreateUser) { + ASSERT_THROW(TypeParam("CREATE USER"), SyntaxException); + ASSERT_THROW(TypeParam("CREATE USER 123"), SyntaxException); + check_auth_query("CREATE USER user", + AuthQuery::Action::CREATE_USER, "user", "", "", + {}, {}); + check_auth_query("CREATE USER user IDENTIFIED BY 'password'", + AuthQuery::Action::CREATE_USER, "user", "", "", + "password", {}); + check_auth_query("CREATE USER user IDENTIFIED BY ''", + AuthQuery::Action::CREATE_USER, "user", "", "", + "", {}); + check_auth_query("CREATE USER user IDENTIFIED BY null", + AuthQuery::Action::CREATE_USER, "user", "", "", + TypedValue::Null, {}); + ASSERT_THROW(TypeParam("CRATE USER user IDENTIFIED BY password"), SyntaxException); + ASSERT_THROW(TypeParam("CREATE USER user IDENTIFIED BY 5"), SyntaxException); + ASSERT_THROW(TypeParam("CREATE USER user IDENTIFIED BY "), SyntaxException); +} + +TYPED_TEST(CypherMainVisitorTest, SetPassword) { + ASSERT_THROW(TypeParam("SET PASSWORD FOR"), SyntaxException); + ASSERT_THROW(TypeParam("SET PASSWORD FOR user "), SyntaxException); + check_auth_query("SET PASSWORD FOR user TO null", + AuthQuery::Action::SET_PASSWORD, "user", "", "", + TypedValue::Null, {}); + check_auth_query("SET PASSWORD FOR user TO 'password'", + AuthQuery::Action::SET_PASSWORD, "user", "", "", + "password", {}); + ASSERT_THROW(TypeParam("SET PASSWORD FOR user To 5"), SyntaxException); } TYPED_TEST(CypherMainVisitorTest, DropUser) { - auto check_drop_user = [](std::string input, - const std::vector &usernames) { - TypeParam ast_generator(input); - auto *query = ast_generator.query_; - ASSERT_TRUE(query->single_query_); - auto *single_query = query->single_query_; - ASSERT_EQ(single_query->clauses_.size(), 1U); - auto *drop_user = dynamic_cast(single_query->clauses_[0]); - ASSERT_TRUE(drop_user); - EXPECT_EQ(drop_user->usernames_, usernames); - }; + ASSERT_THROW(TypeParam("DROP USER"), SyntaxException); + check_auth_query("DROP USER user", AuthQuery::Action::DROP_USER, + "user", "", "", {}, {}); + ASSERT_THROW(TypeParam("DROP USER lagano rolamo"), SyntaxException); +} - EXPECT_THROW(check_drop_user("DrOp USER", {}), SyntaxException); - check_drop_user("DrOP UsEr dominik", {"dominik"}); - check_drop_user("DrOP USER dominik , spomenik", {"dominik", "spomenik"}); - EXPECT_THROW( - check_drop_user("DrOP USER dominik, , spomenik", {"dominik", "spomenik"}), - SyntaxException); - check_drop_user("DrOP USER dominik , spomenik , jackie, jackie , johnny", - {"dominik", "spomenik", "jackie", "jackie", "johnny"}); +TYPED_TEST(CypherMainVisitorTest, ShowUsers) { + ASSERT_THROW(TypeParam("SHOW USERS ROLES"), SyntaxException); + check_auth_query("SHOW USERS", AuthQuery::Action::SHOW_USERS, "", + "", "", {}, {}); +} + +TYPED_TEST(CypherMainVisitorTest, GrantRole) { + ASSERT_THROW(TypeParam("GRANT ROLE"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT ROLE role"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT ROLE role TO"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT ROLE TO user"), SyntaxException); + check_auth_query("GRANT ROLE role TO user", + AuthQuery::Action::GRANT_ROLE, "user", "role", "", + {}, {}); +} + +TYPED_TEST(CypherMainVisitorTest, RevokeRole) { + ASSERT_THROW(TypeParam("REVOKE ROLE"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE ROLE role"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE ROLE role FROM"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE ROLE FROM user"), SyntaxException); + check_auth_query("REVOKE ROLE role FROM user", + AuthQuery::Action::REVOKE_ROLE, "user", "role", + "", {}, {}); +} + +TYPED_TEST(CypherMainVisitorTest, GrantPrivilege) { + ASSERT_THROW(TypeParam("GRANT"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT TO user"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT BLABLA TO user"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT MATCH, TO user"), SyntaxException); + ASSERT_THROW(TypeParam("GRANT MATCH, BLABLA TO user"), SyntaxException); + check_auth_query("GRANT MATCH TO user", + AuthQuery::Action::GRANT_PRIVILEGE, "", "", + "user", {}, {AuthQuery::Privilege::MATCH}); + check_auth_query( + "GRANT MATCH, AUTH TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", + "user", {}, {AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH}); +} + +TYPED_TEST(CypherMainVisitorTest, DenyPrivilege) { + ASSERT_THROW(TypeParam("DENY"), SyntaxException); + ASSERT_THROW(TypeParam("DENY TO user"), SyntaxException); + ASSERT_THROW(TypeParam("DENY BLABLA TO user"), SyntaxException); + ASSERT_THROW(TypeParam("DENY MATCH, TO user"), SyntaxException); + ASSERT_THROW(TypeParam("DENY MATCH, BLABLA TO user"), SyntaxException); + check_auth_query("DENY MATCH TO user", + AuthQuery::Action::DENY_PRIVILEGE, "", "", + "user", {}, {AuthQuery::Privilege::MATCH}); + check_auth_query( + "DENY MATCH, AUTH TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", + "user", {}, {AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH}); +} + +TYPED_TEST(CypherMainVisitorTest, RevokePrivilege) { + ASSERT_THROW(TypeParam("REVOKE"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE FROM user"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE BLABLA FROM user"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE MATCH, FROM user"), SyntaxException); + ASSERT_THROW(TypeParam("REVOKE MATCH, BLABLA FROM user"), SyntaxException); + check_auth_query("REVOKE MATCH FROM user", + AuthQuery::Action::REVOKE_PRIVILEGE, "", "", + "user", {}, {AuthQuery::Privilege::MATCH}); + check_auth_query( + "REVOKE MATCH, AUTH FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", + "", "user", {}, + {AuthQuery::Privilege::MATCH, AuthQuery::Privilege::AUTH}); + check_auth_query( + "REVOKE ALL PRIVILEGES FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, + "", "", "user", {}, + {AuthQuery::Privilege::CREATE, AuthQuery::Privilege::DELETE, + AuthQuery::Privilege::MATCH, AuthQuery::Privilege::MERGE, + AuthQuery::Privilege::SET, AuthQuery::Privilege::AUTH, + AuthQuery::Privilege::STREAM}); +} + +TYPED_TEST(CypherMainVisitorTest, ShowGrants) { + ASSERT_THROW(TypeParam("SHOW GRANTS FOR"), SyntaxException); + check_auth_query("SHOW GRANTS FOR user", + AuthQuery::Action::SHOW_GRANTS, "", "", "user", + {}, {}); + ASSERT_THROW(TypeParam("SHOW GRANTS FOR user1, user2"), SyntaxException); +} + +TYPED_TEST(CypherMainVisitorTest, ShowRoleForUser) { + ASSERT_THROW(TypeParam("SHOW ROLE FOR USER"), SyntaxException); + check_auth_query("SHOW ROLE FOR USER user", + AuthQuery::Action::SHOW_ROLE_FOR_USER, "user", "", + "", {}, {}); + ASSERT_THROW(TypeParam("SHOW ROLE FOR USER user1, user2"), SyntaxException); +} + +TYPED_TEST(CypherMainVisitorTest, ShowUsersForRole) { + ASSERT_THROW(TypeParam("SHOW USERS FOR ROLE"), SyntaxException); + check_auth_query("SHOW USERS FOR ROLE role", + AuthQuery::Action::SHOW_USERS_FOR_ROLE, "", + "role", "", {}, {}); + ASSERT_THROW(TypeParam("SHOW USERS FOR ROLE role1, role2"), SyntaxException); } TYPED_TEST(CypherMainVisitorTest, CreateStream) { diff --git a/tests/unit/query_common.hpp b/tests/unit/query_common.hpp index 277dc77f5..7de256d5c 100644 --- a/tests/unit/query_common.hpp +++ b/tests/unit/query_common.hpp @@ -574,10 +574,9 @@ auto GetMerge(AstStorage &storage, Pattern *pattern, OnMatch on_match, #define EXTRACT(variable, list, expr) \ storage.Create(storage.Create(variable), \ list, expr) -#define CREATE_USER(username, password) \ - storage.Create((username), LITERAL(password), true) -#define ALTER_USER(username, password) \ - storage.Create((username), LITERAL(password), false) +#define AUTH_QUERY(action, user, role, user_or_role, password, privileges) \ + storage.Create((action), (user), (role), (user_or_role), \ + LITERAL(password), (privileges)) #define DROP_USER(usernames) storage.Create((usernames)) #define CREATE_STREAM(stream_name, stream_uri, stream_topic, transform_uri, \ batch_interval, batch_size) \ diff --git a/tests/unit/query_planner.cpp b/tests/unit/query_planner.cpp index ca287304f..8bb666b9a 100644 --- a/tests/unit/query_planner.cpp +++ b/tests/unit/query_planner.cpp @@ -132,8 +132,7 @@ class PlanChecker : public HierarchicalLogicalOperatorVisitor { PRE_VISIT(PullRemoteOrderBy); - VISIT(ModifyUser); - VISIT(DropUser); + VISIT(AuthHandler); VISIT(CreateStream); VISIT(DropStream); @@ -377,6 +376,37 @@ class ExpectScanAllByLabelPropertyRange std::experimental::optional upper_bound_; }; +class ExpectAuthHandler : public OpChecker { + public: + ExpectAuthHandler(query::AuthQuery::Action action, std::string user, + std::string role, std::string user_or_role, + query::Expression *password, + std::vector privileges) + : action_(action), + user_(user), + role_(role), + user_or_role_(user_or_role), + password_(password), + privileges_(privileges) {} + + void ExpectOp(AuthHandler &auth_handler, const SymbolTable &) override { + EXPECT_EQ(auth_handler.action(), action_); + EXPECT_EQ(auth_handler.user(), user_); + EXPECT_EQ(auth_handler.role(), role_); + EXPECT_EQ(auth_handler.user_or_role(), user_or_role_); + EXPECT_TRUE(auth_handler.password()); + EXPECT_EQ(auth_handler.privileges(), privileges_); + } + + private: + query::AuthQuery::Action action_; + std::string user_; + std::string role_; + std::string user_or_role_; + query::Expression *password_{nullptr}; + std::vector privileges_; +}; + class ExpectCreateIndex : public OpChecker { public: ExpectCreateIndex(storage::Label label, storage::Property property) @@ -623,36 +653,6 @@ class Planner { std::unique_ptr plan_; }; -class ExpectModifyUser : public OpChecker { - public: - ExpectModifyUser(std::string username, bool is_create) - : username_(username), is_create_(is_create) {} - - void ExpectOp(ModifyUser &modify_user, const SymbolTable &) override { - EXPECT_EQ(username_, modify_user.username()); - // TODO(mtomic): proper password verification - EXPECT_NE(dynamic_cast(modify_user.password()), - nullptr); - EXPECT_EQ(is_create_, modify_user.is_create()); - } - - private: - std::string username_; - bool is_create_; -}; - -class ExpectDropUser : public OpChecker { - public: - ExpectDropUser(std::vector usernames) : usernames_(usernames) {} - - void ExpectOp(DropUser &drop_user, const SymbolTable &) override { - EXPECT_EQ(usernames_, drop_user.usernames()); - } - - private: - std::vector usernames_; -}; - void SavePlan(const LogicalOperator &plan, ::capnp::MessageBuilder *message) { auto builder = message->initRoot(); LogicalOperator::SaveHelper helper; @@ -2076,8 +2076,9 @@ TYPED_TEST(TestPlanner, WhereIndexedLabelPropertyRange) { AstStorage storage; auto lit_42 = LITERAL(42); auto n_prop = PROPERTY_LOOKUP("n", property); - auto check_planned_range = [&label, &property, &dba]( - const auto &rel_expr, auto lower_bound, auto upper_bound) { + auto check_planned_range = [&label, &property, &dba](const auto &rel_expr, + auto lower_bound, + auto upper_bound) { // Shadow the first storage, so that the query is created in this one. AstStorage storage; QUERY(SINGLE_QUERY(MATCH(PATTERN(NODE("n", label))), WHERE(rel_expr), @@ -2318,36 +2319,25 @@ TYPED_TEST(TestPlanner, ReturnAsteriskOmitsLambdaSymbols) { } } -TYPED_TEST(TestPlanner, ModifyUser) { - { - // Test CREATE USER user WITH PASSWORD 'password' - FakeDbAccessor dba; - AstStorage storage; - QUERY(SINGLE_QUERY(CREATE_USER("user", "password"))); - CheckPlan(storage, ExpectModifyUser("user", true)); - auto expected = - ExpectDistributed(MakeCheckers(ExpectModifyUser("user", true))); - CheckDistributedPlan(storage, expected); - } - { - // Test ALTER USER user WITH PASSWORD 'password' - FakeDbAccessor dba; - AstStorage storage; - QUERY(SINGLE_QUERY(ALTER_USER("user", "password"))); - CheckPlan(storage, ExpectModifyUser("user", false)); - auto expected = - ExpectDistributed(MakeCheckers(ExpectModifyUser("user", false))); - CheckDistributedPlan(storage, expected); - } -} - -TYPED_TEST(TestPlanner, DropUser) { - // Test DROP USER user1, user2, user3 +TYPED_TEST(TestPlanner, AuthQuery) { + // Check if everything is properly forwarded from ast node to the operator + FakeDbAccessor dba; AstStorage storage; - std::vector usernames({"user1", "user2", "user3"}); - QUERY(SINGLE_QUERY(DROP_USER(usernames))); - CheckPlan(storage, ExpectDropUser(usernames)); - auto expected = ExpectDistributed(MakeCheckers(ExpectDropUser(usernames))); + QUERY(SINGLE_QUERY(AUTH_QUERY(query::AuthQuery::Action::DROP_ROLE, "user", + "role", "user_or_role", LITERAL("password"), + std::vector( + {query::AuthQuery::Privilege::MATCH, + query::AuthQuery::Privilege::AUTH})))); + CheckPlan( + storage, ExpectAuthHandler(query::AuthQuery::Action::DROP_ROLE, "user", + "role", "user_or_role", LITERAL("password"), + {query::AuthQuery::Privilege::MATCH, + query::AuthQuery::Privilege::AUTH})); + auto expected = ExpectDistributed(MakeCheckers( + ExpectAuthHandler(query::AuthQuery::Action::DROP_ROLE, "user", "role", + "user_or_role", LITERAL("password"), + {query::AuthQuery::Privilege::MATCH, + query::AuthQuery::Privilege::AUTH}))); CheckDistributedPlan(storage, expected); }