diff --git a/CHANGELOG.md b/CHANGELOG.md index 850e34513..f2d23909c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ * Fixed memory tracking issues. Some of the allocation and deallocation weren't tracked during the query execution. * Fixed reading CSV files that are using CRLF as the newline symbol. +* Fixed permission issues for `LOAD CSV`, `FREE MEMORY`, `LOCK DATA DIRECTORY`, + and replication queries. ## v1.4.0 diff --git a/src/query/frontend/ast/ast.lcp b/src/query/frontend/ast/ast.lcp index b42e07a08..ee4fdcebd 100644 --- a/src/query/frontend/ast/ast.lcp +++ b/src/query/frontend/ast/ast.lcp @@ -2232,6 +2232,7 @@ const std::vector kPrivilegesAll = { AuthQuery::Privilege::AUTH, AuthQuery::Privilege::CONSTRAINT, AuthQuery::Privilege::DUMP, AuthQuery::Privilege::REPLICATION, + AuthQuery::Privilege::READ_FILE, AuthQuery::Privilege::LOCK_PATH, AuthQuery::Privilege::FREE_MEMORY}; cpp<# diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp index 2d5373853..1539422e2 100644 --- a/src/query/frontend/ast/ast_visitor.hpp +++ b/src/query/frontend/ast/ast_visitor.hpp @@ -109,6 +109,6 @@ class ExpressionVisitor template class QueryVisitor : public ::utils::Visitor {}; + ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery> {}; } // namespace query diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 7773ace33..ea0aa3ccd 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -767,6 +767,10 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext if (ctx->AUTH()) return AuthQuery::Privilege::AUTH; if (ctx->CONSTRAINT()) return AuthQuery::Privilege::CONSTRAINT; if (ctx->DUMP()) return AuthQuery::Privilege::DUMP; + if (ctx->REPLICATION()) return AuthQuery::Privilege::REPLICATION; + if (ctx->LOCK_PATH()) return AuthQuery::Privilege::LOCK_PATH; + if (ctx->READ_FILE()) return AuthQuery::Privilege::READ_FILE; + if (ctx->FREE_MEMORY()) return AuthQuery::Privilege::FREE_MEMORY; LOG_FATAL("Should not get here - unknown privilege!"); } diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 index 4d96ace10..8778baf01 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 @@ -117,7 +117,7 @@ delimiter : literal ; quote : literal ; -rowVar : variable ; +rowVar : variable ; userOrRoleName : symbolicName ; @@ -146,8 +146,22 @@ denyPrivilege : DENY ( ALL PRIVILEGES | privileges=privilegeList ) TO userOrRole revokePrivilege : REVOKE ( ALL PRIVILEGES | privileges=privilegeList ) FROM userOrRole=userOrRoleName ; -privilege : CREATE | DELETE | MATCH | MERGE | SET - | REMOVE | INDEX | STATS | AUTH | CONSTRAINT | DUMP ; +privilege : CREATE + | DELETE + | MATCH + | MERGE + | SET + | REMOVE + | INDEX + | STATS + | AUTH + | CONSTRAINT + | DUMP + | REPLICATION + | LOCK_PATH + | READ_FILE + | FREE_MEMORY + ; privilegeList : privilege ( ',' privilege )* ; diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 index fbfc5a2d2..9aeec8eec 100644 --- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 +++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 @@ -10,6 +10,8 @@ lexer grammar MemgraphCypherLexer ; import CypherLexer ; +UNDERSCORE : '_' ; + ALTER : A L T E R ; ASYNC : A S Y N C ; AUTH : A U T H ; @@ -25,6 +27,7 @@ DROP : D R O P ; DUMP : D U M P ; FOR : F O R ; FREE : F R E E ; +FREE_MEMORY : F R E E UNDERSCORE M E M O R Y ; FROM : F R O M ; GRANT : G R A N T ; GRANTS : G R A N T S ; @@ -33,12 +36,14 @@ IDENTIFIED : I D E N T I F I E D ; IGNORE : I G N O R E ; LOAD : L O A D ; LOCK : L O C K ; +LOCK_PATH : L O C K UNDERSCORE P A T H ; MAIN : M A I N ; MODE : M O D E ; NO : N O ; PASSWORD : P A S S W O R D ; PORT : P O R T ; PRIVILEGES : P R I V I L E G E S ; +READ_FILE : R E A D UNDERSCORE F I L E ; REGISTER : R E G I S T E R ; REPLICA : R E P L I C A ; REPLICAS : R E P L I C A S ; diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp index 4cd046bf5..4173c7fbc 100644 --- a/src/query/frontend/semantic/required_privileges.cpp +++ b/src/query/frontend/semantic/required_privileges.cpp @@ -51,74 +51,58 @@ class PrivilegeExtractor : public QueryVisitor, public HierarchicalTreeVis void Visit(LockPathQuery &lock_path_query) override { AddPrivilege(AuthQuery::Privilege::LOCK_PATH); } - void Visit(LoadCsv &load_csv) override { AddPrivilege(AuthQuery::Privilege::READ_FILE); } - void Visit(FreeMemoryQuery &free_memory_query) override { AddPrivilege(AuthQuery::Privilege::FREE_MEMORY); } - void Visit(ReplicationQuery &replication_query) override { - switch (replication_query.action_) { - case ReplicationQuery::Action::SET_REPLICATION_ROLE: - AddPrivilege(AuthQuery::Privilege::REPLICATION); - break; - case ReplicationQuery::Action::SHOW_REPLICATION_ROLE: - AddPrivilege(AuthQuery::Privilege::REPLICATION); - break; - case ReplicationQuery::Action::REGISTER_REPLICA: - AddPrivilege(AuthQuery::Privilege::REPLICATION); - break; - case ReplicationQuery::Action::DROP_REPLICA: - AddPrivilege(AuthQuery::Privilege::REPLICATION); - break; - case ReplicationQuery::Action::SHOW_REPLICAS: - AddPrivilege(AuthQuery::Privilege::REPLICATION); - break; - } - } + void Visit(ReplicationQuery &replication_query) override { AddPrivilege(AuthQuery::Privilege::REPLICATION); } - bool PreVisit(Create &) override { + bool PreVisit(Create & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::CREATE); return false; } - bool PreVisit(CallProcedure &) override { + bool PreVisit(CallProcedure & /*unused*/) override { // TODO: Corresponding privilege return false; } - bool PreVisit(Delete &) override { + bool PreVisit(Delete & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::DELETE); return false; } - bool PreVisit(Match &) override { + bool PreVisit(Match & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::MATCH); return false; } - bool PreVisit(Merge &) override { + bool PreVisit(Merge & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::MERGE); return false; } - bool PreVisit(SetProperty &) override { + bool PreVisit(SetProperty & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::SET); return false; } - bool PreVisit(SetProperties &) override { + bool PreVisit(SetProperties & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::SET); return false; } - bool PreVisit(SetLabels &) override { + bool PreVisit(SetLabels & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::SET); return false; } - bool PreVisit(RemoveProperty &) override { + bool PreVisit(RemoveProperty & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::REMOVE); return false; } - bool PreVisit(RemoveLabels &) override { + bool PreVisit(RemoveLabels & /*unused*/) override { AddPrivilege(AuthQuery::Privilege::REMOVE); return false; } + bool PreVisit(LoadCsv & /*unused*/) override { + AddPrivilege(AuthQuery::Privilege::READ_FILE); + return false; + } - bool Visit(Identifier &) override { return true; } - bool Visit(PrimitiveLiteral &) override { return true; } - bool Visit(ParameterLookup &) override { return true; } + bool Visit(Identifier & /*unused*/) override { return true; } + bool Visit(PrimitiveLiteral & /*unused*/) override { return true; } + bool Visit(ParameterLookup & /*unused*/) override { return true; } private: void AddPrivilege(AuthQuery::Privilege privilege) { diff --git a/src/query/frontend/stripped_lexer_constants.hpp b/src/query/frontend/stripped_lexer_constants.hpp index a73b777a6..c08da89ad 100644 --- a/src/query/frontend/stripped_lexer_constants.hpp +++ b/src/query/frontend/stripped_lexer_constants.hpp @@ -79,15 +79,15 @@ class Trie { const int kBitsetSize = 65536; const trie::Trie kKeywords = { - "union", "all", "optional", "match", "unwind", "as", "merge", "on", "create", - "set", "detach", "delete", "remove", "with", "distinct", "return", "order", "by", - "skip", "limit", "ascending", "asc", "descending", "desc", "where", "or", "xor", - "and", "not", "in", "starts", "ends", "contains", "is", "null", "case", - "when", "then", "else", "end", "count", "filter", "extract", "any", "none", - "single", "true", "false", "reduce", "coalesce", "user", "password", "alter", "drop", - "show", "stats", "unique", "explain", "profile", "storage", "index", "info", "exists", - "assert", "constraint", "node", "key", "dump", "database", "call", "yield", "memory", - "mb", "kb", "unlimited", "free", "procedure", "query"}; + "union", "all", "optional", "match", "unwind", "as", "merge", "on", "create", + "set", "detach", "delete", "remove", "with", "distinct", "return", "order", "by", + "skip", "limit", "ascending", "asc", "descending", "desc", "where", "or", "xor", + "and", "not", "in", "starts", "ends", "contains", "is", "null", "case", + "when", "then", "else", "end", "count", "filter", "extract", "any", "none", + "single", "true", "false", "reduce", "coalesce", "user", "password", "alter", "drop", + "show", "stats", "unique", "explain", "profile", "storage", "index", "info", "exists", + "assert", "constraint", "node", "key", "dump", "database", "call", "yield", "memory", + "mb", "kb", "unlimited", "free", "procedure", "query", "free_memory", "read_file", "lock_path"}; // Unicode codepoints that are allowed at the start of the unescaped name. const std::bitset kUnescapedNameAllowedStarts( diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp index 294cdbf24..e734d1cab 100644 --- a/tests/unit/cypher_main_visitor.cpp +++ b/tests/unit/cypher_main_visitor.cpp @@ -2053,6 +2053,14 @@ TEST_P(CypherMainVisitorTest, GrantPrivilege) { {AuthQuery::Privilege::CONSTRAINT}); check_auth_query(&ast_generator, "GRANT DUMP TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, {AuthQuery::Privilege::DUMP}); + check_auth_query(&ast_generator, "GRANT REPLICATION TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, + {AuthQuery::Privilege::REPLICATION}); + check_auth_query(&ast_generator, "GRANT LOCK_PATH TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, + {AuthQuery::Privilege::LOCK_PATH}); + check_auth_query(&ast_generator, "GRANT READ_FILE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, + {AuthQuery::Privilege::READ_FILE}); + check_auth_query(&ast_generator, "GRANT FREE_MEMORY TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {}, + {AuthQuery::Privilege::FREE_MEMORY}); } TEST_P(CypherMainVisitorTest, DenyPrivilege) { diff --git a/tests/unit/query_required_privileges.cpp b/tests/unit/query_required_privileges.cpp index 2e929ee45..a5afc010c 100644 --- a/tests/unit/query_required_privileges.cpp +++ b/tests/unit/query_required_privileges.cpp @@ -1,6 +1,7 @@ #include #include +#include "query/frontend/ast/ast_visitor.hpp" #include "query/frontend/semantic/required_privileges.hpp" #include "storage/v2/id_types.hpp" @@ -131,3 +132,20 @@ TEST_F(TestPrivilegeExtractor, DumpDatabase) { auto *query = storage.Create(); EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::DUMP)); } + +TEST_F(TestPrivilegeExtractor, ReadFile) { + auto load_csv = storage.Create(); + load_csv->row_var_ = IDENT("row"); + auto *query = QUERY(SINGLE_QUERY(load_csv)); + EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::READ_FILE)); +} + +TEST_F(TestPrivilegeExtractor, LockPathQuery) { + auto *query = storage.Create(); + EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::LOCK_PATH)); +} + +TEST_F(TestPrivilegeExtractor, FreeMemoryQuery) { + auto *query = storage.Create(); + EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::FREE_MEMORY)); +}