From dc8dad97944ec969c35f6f270670274f252adfbf Mon Sep 17 00:00:00 2001 From: niko4299 <51059248+niko4299@users.noreply.github.com> Date: Tue, 13 Sep 2022 17:14:23 +0200 Subject: [PATCH] Add authorization in SetLabels, RemoveLabels, Allshortestpath cursor (#537) --- src/query/plan/operator.cpp | 37 +++- .../create_delete_filtering_tests.py | 43 ++++ .../path_filtering_tests.py | 188 ++++++++++++++++++ .../query_plan_create_set_remove_delete.cpp | 154 ++++++++++++++ tests/unit/query_plan_match_filter_return.cpp | 157 +++++++++++---- 5 files changed, 543 insertions(+), 36 deletions(-) diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp index 1b92cae51..c86b427fc 100644 --- a/src/query/plan/operator.cpp +++ b/src/query/plan/operator.cpp @@ -1870,17 +1870,29 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor { // Populates the priority queue structure with expansions // from the given vertex. skips expansions that don't satisfy // the "where" condition. - auto expand_from_vertex = [this, &expand_vertex](const VertexAccessor &vertex, const TypedValue &weight, - int64_t depth) { + auto expand_from_vertex = [this, &expand_vertex, &context](const VertexAccessor &vertex, const TypedValue &weight, + int64_t depth) { if (self_.common_.direction != EdgeAtom::Direction::IN) { auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : out_edges) { + if (context.auth_checker && + !(context.auth_checker->Has(edge.To(), storage::View::OLD, + memgraph::query::AuthQuery::FineGrainedPrivilege::READ) && + context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) { + continue; + } expand_vertex(edge, EdgeAtom::Direction::OUT, weight, depth); } } if (self_.common_.direction != EdgeAtom::Direction::OUT) { auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types)); for (const auto &edge : in_edges) { + if (context.auth_checker && + !(context.auth_checker->Has(edge.From(), storage::View::OLD, + memgraph::query::AuthQuery::FineGrainedPrivilege::READ) && + context.auth_checker->Has(edge, memgraph::query::AuthQuery::FineGrainedPrivilege::READ))) { + continue; + } expand_vertex(edge, EdgeAtom::Direction::IN, weight, depth); } } @@ -2725,6 +2737,11 @@ SetLabels::SetLabelsCursor::SetLabelsCursor(const SetLabels &self, utils::Memory bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) { SCOPED_PROFILE_OP("SetLabels"); + if (context.auth_checker && + !context.auth_checker->Has(self_.labels_, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { + throw QueryRuntimeException("Couldn't set label due to not having enough permission!"); + } + if (!input_cursor_->Pull(frame, context)) return false; TypedValue &vertex_value = frame[self_.input_symbol_]; @@ -2732,6 +2749,12 @@ bool SetLabels::SetLabelsCursor::Pull(Frame &frame, ExecutionContext &context) { if (vertex_value.IsNull()) return true; ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); auto &vertex = vertex_value.ValueVertex(); + + if (context.auth_checker && !context.auth_checker->Has(vertex, storage::View::OLD, + memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) { + throw QueryRuntimeException("Couldn't set label due to not having enough permission!"); + } + for (auto label : self_.labels_) { auto maybe_value = vertex.AddLabel(label); if (maybe_value.HasError()) { @@ -2865,6 +2888,11 @@ RemoveLabels::RemoveLabelsCursor::RemoveLabelsCursor(const RemoveLabels &self, u bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &context) { SCOPED_PROFILE_OP("RemoveLabels"); + if (context.auth_checker && + !context.auth_checker->Has(self_.labels_, memgraph::query::AuthQuery::FineGrainedPrivilege::CREATE_DELETE)) { + throw QueryRuntimeException("Couldn't remove label due to not having enough permission!"); + } + if (!input_cursor_->Pull(frame, context)) return false; TypedValue &vertex_value = frame[self_.input_symbol_]; @@ -2872,6 +2900,11 @@ bool RemoveLabels::RemoveLabelsCursor::Pull(Frame &frame, ExecutionContext &cont if (vertex_value.IsNull()) return true; ExpectType(self_.input_symbol_, vertex_value, TypedValue::Type::Vertex); auto &vertex = vertex_value.ValueVertex(); + if (context.auth_checker && !context.auth_checker->Has(vertex, storage::View::OLD, + memgraph::query::AuthQuery::FineGrainedPrivilege::UPDATE)) { + throw QueryRuntimeException("Couldn't remove label due to not having enough permission!"); + } + for (auto label : self_.labels_) { auto maybe_value = vertex.RemoveLabel(label); if (maybe_value.HasError()) { diff --git a/tests/e2e/fine_grained_access/create_delete_filtering_tests.py b/tests/e2e/fine_grained_access/create_delete_filtering_tests.py index 3e0e5118e..1eb607715 100644 --- a/tests/e2e/fine_grained_access/create_delete_filtering_tests.py +++ b/tests/e2e/fine_grained_access/create_delete_filtering_tests.py @@ -470,5 +470,48 @@ def test_merge_edge_second_node_label_granted(): ) +def test_set_label_when_label_granted(): + admin_connection = common.connect(username="admin", password="test") + user_connection = common.connect(username="user", password="test") + common.reset_and_prepare(admin_connection.cursor()) + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :update_label_2 TO user;") + + common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) SET p:update_label_2;") + + +def test_set_label_when_label_denied(): + admin_connection = common.connect(username="admin", password="test") + user_connection = common.connect(username="user", password="test") + + common.reset_and_prepare(admin_connection.cursor()) + common.execute_and_fetch_all(admin_connection.cursor(), "DENY CREATE_DELETE ON LABELS :update_label_2 TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete TO user;") + + with pytest.raises(DatabaseError): + common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) SET p:update_label_2;") + + +def test_remove_label_when_label_granted(): + admin_connection = common.connect(username="admin", password="test") + user_connection = common.connect(username="user", password="test") + + common.reset_and_prepare(admin_connection.cursor()) + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT CREATE_DELETE ON LABELS :test_delete TO user;") + + common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;") + + +def test_remove_label_when_label_denied(): + admin_connection = common.connect(username="admin", password="test") + user_connection = common.connect(username="user", password="test") + + common.reset_and_prepare(admin_connection.cursor()) + common.execute_and_fetch_all(admin_connection.cursor(), "DENY CREATE_DELETE ON LABELS :update_label_2 TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS :test_delete TO user;") + + with pytest.raises(DatabaseError): + common.execute_and_fetch_all(user_connection.cursor(), "MATCH (p:test_delete) REMOVE p:test_delete;") + + if __name__ == "__main__": sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/e2e/fine_grained_access/path_filtering_tests.py b/tests/e2e/fine_grained_access/path_filtering_tests.py index 5881537bd..311168408 100644 --- a/tests/e2e/fine_grained_access/path_filtering_tests.py +++ b/tests/e2e/fine_grained_access/path_filtering_tests.py @@ -525,5 +525,193 @@ def test_bfs_single_source_denied_edge_type_3(): assert source_destination_path[0][0] == expected_path +def test_all_shortest_paths_when_all_edge_types_all_labels_granted(): + admin_connection = common.connect(username="admin", password="test") + user_connnection = common.connect(username="user", password="test") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;") + + total_paths_results = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);", + ) + path_result = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length,nodes(p);", + ) + + expected_path = [0, 1, 3, 4, 5] + expected_all_paths = [ + [0, 1], + [0, 1, 2], + [0, 1, 3], + [0, 1, 3, 4], + [0, 1, 3, 4, 5], + [1, 2], + [1, 3], + [1, 3, 4], + [1, 3, 4, 5], + [2, 1], + [2, 3], + [2, 3, 4], + [2, 3, 4, 5], + [3, 4], + [3, 4, 5], + [4, 3], + [4, 5], + ] + + assert len(total_paths_results) == 16 + assert all(path[0] in expected_all_paths for path in total_paths_results) + assert path_result[0][0] == 20 + assert all(node.id in expected_path for node in path_result[0][1]) + + +def test_all_shortest_paths_when_all_edge_types_all_labels_denied(): + admin_connection = common.connect(username="admin", password="test") + user_connnection = common.connect(username="user", password="test") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "DENY READ ON LABELS * TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "DENY READ ON EDGE_TYPES * TO user;") + + results = common.execute_and_fetch_all( + user_connnection.cursor(), "MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN p;" + ) + + assert len(results) == 0 + + +def test_all_shortest_paths_when_denied_start(): + admin_connection = common.connect(username="admin", password="test") + user_connnection = common.connect(username="user", password="test") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;") + common.execute_and_fetch_all( + admin_connection.cursor(), "GRANT READ ON LABELS :label1, :label2, :label3, :label4 TO user;" + ) + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "DENY READ ON LABELS :label0 TO user;") + + path_length_result = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;", + ) + + assert len(path_length_result) == 0 + + +def test_all_shortest_paths_when_denied_destination(): + admin_connection = common.connect(username="admin", password="test") + user_connnection = common.connect(username="user", password="test") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;") + common.execute_and_fetch_all( + admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label1, :label2, :label3 TO user;" + ) + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "DENY READ ON LABELS :label4 TO user;") + + path_length_result = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length;", + ) + + assert len(path_length_result) == 0 + + +def test_all_shortest_paths_when_denied_label_1(): + admin_connection = common.connect(username="admin", password="test") + user_connnection = common.connect(username="user", password="test") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;") + common.execute_and_fetch_all( + admin_connection.cursor(), "GRANT READ ON LABELS :label0, :label2, :label3, :label4 TO user;" + ) + common.execute_and_fetch_all(admin_connection.cursor(), "DENY READ ON LABELS :label1 TO user;") + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON EDGE_TYPES * TO user;") + + total_paths_results = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);", + ) + + path_result = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);", + ) + + expected_path = [0, 2, 3, 4, 5] + + expected_all_paths = [ + [0, 2], + [0, 2, 3], + [0, 2, 3, 4], + [0, 2, 3, 4, 5], + [2, 3], + [2, 3, 4], + [2, 3, 4, 5], + [3, 4], + [3, 4, 5], + [4, 3], + [4, 5], + ] + + assert len(total_paths_results) == 11 + assert all(path[0] in expected_all_paths for path in total_paths_results) + assert path_result[0][0] == 30 + assert all(node.id in expected_path for node in path_result[0][1]) + + +def test_all_shortest_paths_when_denied_edge_type_3(): + admin_connection = common.connect(username="admin", password="test") + user_connnection = common.connect(username="user", password="test") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE LABELS * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "REVOKE EDGE_TYPES * FROM user;") + common.execute_and_fetch_all(admin_connection.cursor(), "GRANT READ ON LABELS * TO user;") + common.execute_and_fetch_all( + admin_connection.cursor(), "GRANT READ ON EDGE_TYPES :edge_type_1, :edge_type_2, :edge_type_4 TO user;" + ) + common.execute_and_fetch_all(admin_connection.cursor(), "DENY READ ON EDGE_TYPES :edge_type_3 TO user;") + + path_result = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n:label0)-[r *allShortest (r, n | r.weight) path_length]->(m:label4) RETURN path_length, nodes(p);", + ) + + total_paths_results = common.execute_and_fetch_all( + user_connnection.cursor(), + "MATCH p=(n)-[r *allShortest (r, n | r.weight)]->(m) RETURN extract( node in nodes(p) | node.id);", + ) + + expected_path = [0, 1, 2, 3, 5] + expected_all_paths = [ + [0, 1], + [0, 1, 2], + [0, 1, 2, 4], + [0, 1, 2, 4, 3], + [0, 1, 2, 4, 5], + [1, 2, 4, 3], + [1, 2], + [1, 2, 4], + [1, 2, 4, 5], + [2, 1], + [2, 4, 3], + [2, 4], + [2, 4, 5], + [3, 4], + [3, 4, 5], + [4, 3], + [4, 5], + ] + + assert len(total_paths_results) == 16 + assert all(path[0] in expected_all_paths for path in total_paths_results) + assert path_result[0][0] == 25 + assert all(node.id in expected_path for node in path_result[0][1]) + + if __name__ == "__main__": sys.exit(pytest.main([__file__, "-rA"])) diff --git a/tests/unit/query_plan_create_set_remove_delete.cpp b/tests/unit/query_plan_create_set_remove_delete.cpp index f423aa1f6..72cff71f8 100644 --- a/tests/unit/query_plan_create_set_remove_delete.cpp +++ b/tests/unit/query_plan_create_set_remove_delete.cpp @@ -1242,6 +1242,80 @@ TEST(QueryPlan, SetLabels) { } } +TEST(QueryPlan, SetLabelsWithFineGrained) { + auto set_labels = [&](memgraph::auth::User user, memgraph::query::DbAccessor dba, + std::vector<memgraph::storage::LabelId> labels) { + ASSERT_TRUE(dba.InsertVertex().AddLabel(labels[0]).HasValue()); + ASSERT_TRUE(dba.InsertVertex().AddLabel(labels[0]).HasValue()); + dba.AdvanceCommand(); + + AstStorage storage; + SymbolTable symbol_table; + + auto n = MakeScanAll(storage, symbol_table, "n"); + auto label_set = + std::make_shared<plan::SetLabels>(n.op_, n.sym_, std::vector<memgraph::storage::LabelId>{labels[1], labels[2]}); + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + + PullAll(*label_set, &context); + }; + + // All labels granted + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("*", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + memgraph::storage::Storage db; + auto storage_dba = db.Access(); + memgraph::query::DbAccessor dba(&storage_dba); + auto label1 = dba.NameToLabel("label1"); + auto label2 = dba.NameToLabel("label2"); + auto label3 = dba.NameToLabel("label3"); + set_labels(user, dba, std::vector<memgraph::storage::LabelId>{label1, label2, label3}); + for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + EXPECT_EQ(3, vertex.Labels(memgraph::storage::View::NEW)->size()); + EXPECT_TRUE(*vertex.HasLabel(memgraph::storage::View::NEW, label2)); + EXPECT_TRUE(*vertex.HasLabel(memgraph::storage::View::NEW, label3)); + } + } + + // All labels denied + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Deny("*", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + memgraph::storage::Storage db; + auto storage_dba = db.Access(); + memgraph::query::DbAccessor dba(&storage_dba); + auto label1 = dba.NameToLabel("label1"); + auto label2 = dba.NameToLabel("label2"); + auto label3 = dba.NameToLabel("label3"); + ASSERT_THROW(set_labels(user, dba, std::vector<memgraph::storage::LabelId>{label1, label2, label3}), + QueryRuntimeException); + } + + // label2 denied + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("label1", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + user.fine_grained_access_handler().label_permissions().Deny("label2", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + user.fine_grained_access_handler().label_permissions().Grant("label3", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + + memgraph::storage::Storage db; + auto storage_dba = db.Access(); + memgraph::query::DbAccessor dba(&storage_dba); + auto label1 = dba.NameToLabel("label1"); + auto label2 = dba.NameToLabel("label2"); + auto label3 = dba.NameToLabel("label3"); + ASSERT_THROW(set_labels(user, dba, std::vector<memgraph::storage::LabelId>{label1, label2, label3}), + QueryRuntimeException); + } +} + TEST(QueryPlan, RemoveProperty) { memgraph::storage::Storage db; auto storage_dba = db.Access(); @@ -1339,6 +1413,86 @@ TEST(QueryPlan, RemoveLabels) { } } +TEST(QueryPlan, RemoveLabelsFineGrainedFiltering) { + auto remove_labels = [&](memgraph::auth::User user, memgraph::query::DbAccessor dba, + std::vector<memgraph::storage::LabelId> labels) { + auto v1 = dba.InsertVertex(); + ASSERT_TRUE(v1.AddLabel(labels[0]).HasValue()); + ASSERT_TRUE(v1.AddLabel(labels[1]).HasValue()); + ASSERT_TRUE(v1.AddLabel(labels[2]).HasValue()); + auto v2 = dba.InsertVertex(); + ASSERT_TRUE(v2.AddLabel(labels[0]).HasValue()); + ASSERT_TRUE(v2.AddLabel(labels[2]).HasValue()); + dba.AdvanceCommand(); + + AstStorage storage; + SymbolTable symbol_table; + + auto n = MakeScanAll(storage, symbol_table, "n"); + auto label_remove = std::make_shared<plan::RemoveLabels>( + n.op_, n.sym_, std::vector<memgraph::storage::LabelId>{labels[0], labels[1]}); + memgraph::glue::FineGrainedAuthChecker auth_checker{user, &dba}; + + auto context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + + PullAll(*label_remove, &context); + }; + + // All labels granted + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("*", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + memgraph::storage::Storage db; + auto storage_dba = db.Access(); + memgraph::query::DbAccessor dba(&storage_dba); + auto label1 = dba.NameToLabel("label1"); + auto label2 = dba.NameToLabel("label2"); + auto label3 = dba.NameToLabel("label3"); + remove_labels(user, dba, std::vector<memgraph::storage::LabelId>{label1, label2, label3}); + for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) { + EXPECT_EQ(1, vertex.Labels(memgraph::storage::View::NEW)->size()); + EXPECT_FALSE(*vertex.HasLabel(memgraph::storage::View::NEW, label2)); + EXPECT_TRUE(*vertex.HasLabel(memgraph::storage::View::NEW, label3)); + } + } + + // All labels denied + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Deny("*", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + memgraph::storage::Storage db; + auto storage_dba = db.Access(); + memgraph::query::DbAccessor dba(&storage_dba); + auto label1 = dba.NameToLabel("label1"); + auto label2 = dba.NameToLabel("label2"); + auto label3 = dba.NameToLabel("label3"); + ASSERT_THROW(remove_labels(user, dba, std::vector<memgraph::storage::LabelId>{label1, label2, label3}), + QueryRuntimeException); + } + + // label2 denied + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("label1", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + user.fine_grained_access_handler().label_permissions().Deny("label2", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + user.fine_grained_access_handler().label_permissions().Grant("label3", + memgraph::auth::FineGrainedPermission::CREATE_DELETE); + + memgraph::storage::Storage db; + auto storage_dba = db.Access(); + memgraph::query::DbAccessor dba(&storage_dba); + auto label1 = dba.NameToLabel("label1"); + auto label2 = dba.NameToLabel("label2"); + auto label3 = dba.NameToLabel("label3"); + ASSERT_THROW(remove_labels(user, dba, std::vector<memgraph::storage::LabelId>{label1, label2, label3}), + QueryRuntimeException); + } +} + TEST(QueryPlan, NodeFilterSet) { memgraph::storage::Storage db; auto storage_dba = db.Access(); diff --git a/tests/unit/query_plan_match_filter_return.cpp b/tests/unit/query_plan_match_filter_return.cpp index ece795e2c..9a6c48b9c 100644 --- a/tests/unit/query_plan_match_filter_return.cpp +++ b/tests/unit/query_plan_match_filter_return.cpp @@ -2030,6 +2030,106 @@ TEST_F(QueryPlanExpandWeightedShortestPath, NegativeUpperBound) { EXPECT_THROW(ExpandWShortest(EdgeAtom::Direction::BOTH, -1, LITERAL(true)), QueryRuntimeException); } +TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { + // All edge_types and labels allowed + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + EXPECT_EQ(results[0].path.size(), 1); + EXPECT_EQ(GetDoubleProp(results[0].path[0]), 3); + EXPECT_EQ(results[0].total_weight, 3); + + EXPECT_EQ(results[1].path.size(), 1); + EXPECT_EQ(GetDoubleProp(results[1].path[0]), 5); + EXPECT_EQ(results[1].total_weight, 5); + + EXPECT_EQ(results[2].path.size(), 2); + EXPECT_EQ(GetDoubleProp(results[2].path[0]), 3); + EXPECT_EQ(GetDoubleProp(results[2].path[1]), 3); + EXPECT_EQ(results[2].total_weight, 6); + + EXPECT_EQ(results[3].path.size(), 3); + EXPECT_EQ(GetDoubleProp(results[3].path[0]), 3); + EXPECT_EQ(GetDoubleProp(results[3].path[1]), 3); + EXPECT_EQ(GetDoubleProp(results[3].path[2]), 3); + EXPECT_EQ(results[3].total_weight, 9); + } + + // Denied all labels + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Deny("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(results.size(), 0); + } + + // Denied all edge types + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Deny("*", memgraph::auth::FineGrainedPermission::READ); + auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(results.size(), 0); + } + + // Denied first vertex label + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Deny("l0", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + + auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(results.size(), 0); + } + + // Denied vertex label 2 + { + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("l0", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("l1", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("l2", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("l3", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().label_permissions().Grant("l4", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + + auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(results.size(), 4); + + user.fine_grained_access_handler().label_permissions().Deny("l2", memgraph::auth::FineGrainedPermission::READ); + auto filtered_results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(filtered_results.size(), 3); + } + + // Deny edge type (created vertex 5 and edge vertex 4 to vertex 5) + { + v.push_back(dba.InsertVertex()); + ASSERT_TRUE(v.back().SetProperty(prop.second, memgraph::storage::PropertyValue(5)).HasValue()); + ASSERT_TRUE(v.back().AddLabel(db.NameToLabel("l5")).HasValue()); + dba.AdvanceCommand(); + memgraph::storage::EdgeTypeId edge_type_filter = dba.NameToEdgeType("edge_type_filter"); + auto edge = dba.InsertEdge(&v[4], &v[5], edge_type_filter); + ASSERT_TRUE(edge->SetProperty(prop.second, memgraph::storage::PropertyValue(1)).HasValue()); + e.emplace(std::make_pair(4, 5), *edge); + dba.AdvanceCommand(); + + memgraph::auth::User user{"test"}; + user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(results.size(), 5); + + user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type", + memgraph::auth::FineGrainedPermission::READ); + user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_filter", + memgraph::auth::FineGrainedPermission::READ); + auto filtered_results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(filtered_results.size(), 4); + } +} + /** A test fixture for all shortest paths expansion */ class QueryPlanExpandAllShortestPaths : public testing::Test { public: @@ -2069,6 +2169,8 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { for (int i = 0; i < 5; i++) { v.push_back(dba.InsertVertex()); ASSERT_TRUE(v.back().SetProperty(prop.second, memgraph::storage::PropertyValue(i)).HasValue()); + auto label = fmt::format("l{}", i); + ASSERT_TRUE(v.back().AddLabel(db.NameToLabel(label)).HasValue()); } auto add_edge = [&](int from, int to, double weight) { @@ -2091,7 +2193,8 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { // params returns a vector of pairs. each pair is (vector-of-edges, // vertex) auto ExpandAllShortest(EdgeAtom::Direction direction, std::optional<int> max_depth, Expression *where, - std::optional<int> node_id = 0, ScanAllTuple *existing_node_input = nullptr) { + std::optional<int> node_id = 0, ScanAllTuple *existing_node_input = nullptr, + const memgraph::auth::User *user = nullptr) { // scan the nodes optionally filtering on property value auto n = MakeScanAll(storage, symbol_table, "n", existing_node_input ? existing_node_input->op_ : nullptr); auto last_op = n.op_; @@ -2114,7 +2217,13 @@ class QueryPlanExpandAllShortestPaths : public testing::Test { Frame frame(symbol_table.max_position()); auto cursor = last_op->MakeCursor(memgraph::utils::NewDeleteResource()); std::vector<ResultType> results; - auto context = MakeContext(storage, symbol_table, &dba); + ExecutionContext context; + if (user) { + memgraph::glue::FineGrainedAuthChecker auth_checker{*user, &dba}; + context = MakeContextWithFineGrainedChecker(storage, symbol_table, &dba, &auth_checker); + } else { + context = MakeContext(storage, symbol_table, &dba); + } while (cursor->Pull(frame, context)) { results.push_back(ResultType{std::vector<memgraph::query::EdgeAccessor>(), frame[node_sym].ValueVertex(), frame[total_weight].ValueDouble()}); @@ -2383,48 +2492,27 @@ TEST_F(QueryPlanExpandAllShortestPaths, MultiEdge) { EXPECT_EQ(results[5].total_weight, 9); } -TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { +TEST_F(QueryPlanExpandAllShortestPaths, BasicWithFineGrainedFiltering) { // All edge_types and labels allowed { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true)); + sort(results.begin(), results.end(), compareResultType); + EXPECT_EQ(results[0].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[0].path[0]), 3); - EXPECT_EQ(results[0].total_weight, 3); - EXPECT_EQ(results[1].path.size(), 1); - EXPECT_EQ(GetDoubleProp(results[1].path[0]), 5); - EXPECT_EQ(results[1].total_weight, 5); - EXPECT_EQ(results[2].path.size(), 2); - EXPECT_EQ(GetDoubleProp(results[2].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[2].path[1]), 3); - EXPECT_EQ(results[2].total_weight, 6); - EXPECT_EQ(results[3].path.size(), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[0]), 3); - EXPECT_EQ(GetDoubleProp(results[3].path[1]), 3); EXPECT_EQ(GetDoubleProp(results[3].path[2]), 3); - EXPECT_EQ(results[3].total_weight, 9); } - // Denied all labels - { - memgraph::auth::User user{"test"}; - user.fine_grained_access_handler().label_permissions().Deny("*", memgraph::auth::FineGrainedPermission::READ); - user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); - ASSERT_EQ(results.size(), 0); - } - - // Denied all edge types { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Deny("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2433,8 +2521,8 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Deny("l0", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); + auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 0); } @@ -2448,11 +2536,11 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { user.fine_grained_access_handler().label_permissions().Grant("l4", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 4); - user.fine_grained_access_handler().label_permissions().Deny("l2", memgraph::auth::FineGrainedPermission::READ); - auto filtered_results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto filtered_results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(filtered_results.size(), 3); } @@ -2471,14 +2559,15 @@ TEST_F(QueryPlanExpandWeightedShortestPath, FineGrainedFiltering) { memgraph::auth::User user{"test"}; user.fine_grained_access_handler().label_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Grant("*", memgraph::auth::FineGrainedPermission::READ); - auto results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); ASSERT_EQ(results.size(), 5); user.fine_grained_access_handler().edge_type_permissions().Grant("edge_type", memgraph::auth::FineGrainedPermission::READ); user.fine_grained_access_handler().edge_type_permissions().Deny("edge_type_filter", memgraph::auth::FineGrainedPermission::READ); - auto filtered_results = ExpandWShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + auto filtered_results = ExpandAllShortest(EdgeAtom::Direction::BOTH, 1000, LITERAL(true), 0, nullptr, &user); + ASSERT_EQ(filtered_results.size(), 4); } }