From 73378cd77db729db0fd8eaf430e3d739291edbf0 Mon Sep 17 00:00:00 2001 From: alsu Date: Thu, 23 Jan 2025 11:52:39 +0100 Subject: [PATCH] add separate test to check new CHECK privilege --- rbac/regression.py | 5 + rbac/requirements/requirements.md | 1 + rbac/requirements/requirements.py | 4 +- rbac/tests/privileges/check/check_table.py | 155 +++++++++++++++++++++ rbac/tests/privileges/feature.py | 7 +- 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 rbac/tests/privileges/check/check_table.py diff --git a/rbac/regression.py b/rbac/regression.py index 74accca67..dbecbf3be 100755 --- a/rbac/regression.py +++ b/rbac/regression.py @@ -368,6 +368,11 @@ "multiple authentication methods were introduced in 24.12", check_clickhouse_version("<24.12"), ), + "/rbac/privileges/check table": ( + Skip, + "check privilege was introduced in 25.1", + check_clickhouse_version("<25.1"), + ), } diff --git a/rbac/requirements/requirements.md b/rbac/requirements/requirements.md index f0710b309..304ca7ee3 100644 --- a/rbac/requirements/requirements.md +++ b/rbac/requirements/requirements.md @@ -5120,6 +5120,7 @@ version: 1.0 [ClickHouse] SHALL successfully execute `CHECK table` statement if and only if the user has **show tables** privilege, or any privilege on the table either directly or through a role. +From version 25.1 CHECK TABLE queries require a separate, CHECK grant (PR #74471). #### RQ.SRS-006.RBAC.ShowDatabases.Privilege version: 1.0 diff --git a/rbac/requirements/requirements.py b/rbac/requirements/requirements.py index 78fa368fa..b7788df5d 100644 --- a/rbac/requirements/requirements.py +++ b/rbac/requirements/requirements.py @@ -1,6 +1,6 @@ # These requirements were auto generated # from software requirements specification (SRS) -# document by TestFlows v2.0.240708.1162538. +# document by TestFlows v2.0.241127.1225014. # Do not edit by hand but re-generate instead # using 'tfs requirements generate' command. from testflows.core import Specification @@ -9917,6 +9917,7 @@ description=( "[ClickHouse] SHALL successfully execute `CHECK table` statement if and only if the user has **show tables** privilege,\n" "or any privilege on the table either directly or through a role.\n" + "From version 25.1 CHECK TABLE queries require a separate, CHECK grant (PR #74471).\n" "\n" ), link=None, @@ -19334,6 +19335,7 @@ class targetNode target_table3; [ClickHouse] SHALL successfully execute `CHECK table` statement if and only if the user has **show tables** privilege, or any privilege on the table either directly or through a role. +From version 25.1 CHECK TABLE queries require a separate, CHECK grant (PR #74471). #### RQ.SRS-006.RBAC.ShowDatabases.Privilege version: 1.0 diff --git a/rbac/tests/privileges/check/check_table.py b/rbac/tests/privileges/check/check_table.py new file mode 100644 index 000000000..0c480de59 --- /dev/null +++ b/rbac/tests/privileges/check/check_table.py @@ -0,0 +1,155 @@ +from testflows.core import * +from testflows.asserts import error + +from rbac.requirements import * +from rbac.helper.common import * +import rbac.helper.errors as errors + + +@TestSuite +def table_privileges_granted_directly(self, node=None): + """Check that a user is able to execute `CHECK TABLE` command on a table + if and only if he has CHECK privilege on that table granted directly. + """ + + user_name = f"user_{getuid()}" + + if node is None: + node = self.context.node + + with user(node, f"{user_name}"): + table_name = f"table_name_{getuid()}" + + Suite( + run=check_privilege, + examples=Examples( + "privilege on grant_target_name user_name table_name", + [ + tuple(list(row) + [user_name, user_name, table_name]) + for row in check_privilege.examples + ], + args=Args(name="check privilege={privilege}", format_name=True), + ), + ) + + +@TestSuite +def table_privileges_granted_via_role(self, node=None): + """Check that a user is able to execute `CHECK TABLE` command on a table + if and only if he has CHECK privilege on that table granted via role. + """ + + user_name = f"user_{getuid()}" + role_name = f"role_{getuid()}" + + if node is None: + node = self.context.node + + with user(node, f"{user_name}"), role(node, f"{role_name}"): + table_name = f"table_name_{getuid()}" + + with When("I grant the role to the user"): + node.query(f"GRANT {role_name} TO {user_name}") + + Suite( + run=check_privilege, + examples=Examples( + "privilege on grant_target_name user_name table_name", + [ + tuple(list(row) + [role_name, user_name, table_name]) + for row in check_privilege.examples + ], + args=Args(name="check privilege={privilege}", format_name=True), + ), + ) + + +@TestOutline(Suite) +@Examples( + "privilege on", + [ + ("ALL", "*.*"), + ("CHECK", "*.*"), + ("CHECK", "table"), + ], +) +def check_privilege( + self, privilege, on, grant_target_name, user_name, table_name, node=None +): + """Run checks for commands that require CHECK privilege.""" + + if node is None: + node = self.context.node + + Suite(test=check)( + privilege=privilege, + on=on, + grant_target_name=grant_target_name, + user_name=user_name, + table_name=table_name, + ) + + +@TestSuite +@Requirements( + RQ_SRS_006_RBAC_CheckTable_RequiredPrivilege("1.0"), +) +def check(self, privilege, on, grant_target_name, user_name, table_name, node=None): + """Check that user is able to execute CHECK on a table if and only if the + user has CHECK TABLE privilege on that table. + """ + exitcode, message = errors.not_enough_privileges(name=user_name) + + if node is None: + node = self.context.node + + if on == "table": + on = f"{table_name}" + + with table(node, table_name): + with Scenario("CHECK without privilege"): + with When("I grant the user NONE privilege"): + node.query(f"GRANT NONE TO {grant_target_name}") + + with And("I grant the user USAGE privilege"): + node.query(f"GRANT USAGE ON *.* TO {grant_target_name}") + + with Then(f"I CHECK {table_name}"): + node.query( + f"CHECK TABLE {table_name}", + settings=[("user", user_name)], + exitcode=exitcode, + message=message, + ) + + with Scenario("CHECK with privilege"): + with When(f"I grant {privilege} on the table"): + node.query(f"GRANT {privilege} ON {on} TO {grant_target_name}") + + with Then(f"I CHECK {table_name}"): + node.query(f"CHECK TABLE {table_name}", settings=[("user", user_name)]) + + with Scenario("CHECK with revoked privilege"): + with When(f"I grant {privilege} on the table"): + node.query(f"GRANT {privilege} ON {on} TO {grant_target_name}") + + with And(f"I revoke {privilege} on the table"): + node.query(f"REVOKE {privilege} ON {on} FROM {grant_target_name}") + + with Then(f"I CHECK {table_name}"): + node.query( + f"CHECK TABLE {table_name}", + settings=[("user", user_name)], + exitcode=exitcode, + message=message, + ) + + +@TestFeature +@Name("check table") +def feature(self, node="clickhouse1"): + """Check the RBAC functionality of SHOW TABLES.""" + self.context.node = self.context.cluster.node(node) + + Suite(run=table_privileges_granted_directly, setup=instrument_clickhouse_server_log) + Suite(run=table_privileges_granted_via_role, setup=instrument_clickhouse_server_log) diff --git a/rbac/tests/privileges/feature.py b/rbac/tests/privileges/feature.py index 4d98cb6ed..0a971dabe 100755 --- a/rbac/tests/privileges/feature.py +++ b/rbac/tests/privileges/feature.py @@ -84,7 +84,12 @@ def feature(self): parallel=True, executor=pool, ) - + + Feature( + run=load("rbac.tests.privileges.check.check_table", "feature"), + parallel=True, + executor=pool, + ) Feature( run=load("rbac.tests.privileges.show.show_tables", "feature"), parallel=True,