import pytest from helpers.cluster import ClickHouseCluster from helpers.test_tools import TSV cluster = ClickHouseCluster(__file__) instance = cluster.add_instance( "instance", user_configs=[ "configs/users.d/users.xml", ], ) @pytest.fixture(scope="module", autouse=True) def start_cluster(): try: cluster.start() instance.query("CREATE DATABASE test") instance.query( "CREATE TABLE test.table(x UInt32, y UInt32) ENGINE = MergeTree ORDER BY tuple()" ) instance.query( "CREATE TABLE test.table2(x UInt32, y UInt32) ENGINE = MergeTree ORDER BY tuple()" ) instance.query("INSERT INTO test.table VALUES (1,5), (2,10)") yield cluster finally: cluster.shutdown() @pytest.fixture(autouse=True) def cleanup_after_test(): try: yield finally: instance.query("DROP USER IF EXISTS A, B, C") instance.query("DROP TABLE IF EXISTS test.view_1") def test_smoke(): instance.query("CREATE USER A") assert "Not enough privileges" in instance.query_and_get_error( "SELECT * FROM test.table", user="A" ) instance.query("GRANT SELECT ON test.table TO A") assert instance.query("SELECT * FROM test.table", user="A") == "1\t5\n2\t10\n" instance.query("REVOKE SELECT ON test.table FROM A") assert "Not enough privileges" in instance.query_and_get_error( "SELECT * FROM test.table", user="A" ) def test_grant_option(): instance.query("CREATE USER A") instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO A") assert instance.query("SELECT * FROM test.table", user="A") == "1\t5\n2\t10\n" assert "Not enough privileges" in instance.query_and_get_error( "GRANT SELECT ON test.table TO B", user="A" ) instance.query("GRANT SELECT ON test.table TO A WITH GRANT OPTION") instance.query("GRANT SELECT ON test.table TO B", user="A") assert instance.query("SELECT * FROM test.table", user="B") == "1\t5\n2\t10\n" instance.query("REVOKE SELECT ON test.table FROM A, B") def test_revoke_requires_grant_option(): instance.query("CREATE USER A") instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" expected_error = "Not enough privileges" assert expected_error in instance.query_and_get_error( "REVOKE SELECT ON test.table FROM B", user="A" ) assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("GRANT SELECT ON test.table TO A") expected_error = "privileges have been granted, but without grant option" assert expected_error in instance.query_and_get_error( "REVOKE SELECT ON test.table FROM B", user="A" ) assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("GRANT SELECT ON test.table TO A WITH GRANT OPTION") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("REVOKE SELECT ON test.table FROM B", user="A") assert instance.query("SHOW GRANTS FOR B") == "" instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("REVOKE SELECT ON test.* FROM B", user="A") assert instance.query("SHOW GRANTS FOR B") == "" instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("REVOKE ALL ON test.* FROM B", user="A") assert instance.query("SHOW GRANTS FOR B") == "" instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("REVOKE ALL ON *.* FROM B", user="A") assert instance.query("SHOW GRANTS FOR B") == "" instance.query("REVOKE GRANT OPTION FOR ALL ON *.* FROM A") instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" expected_error = "privileges have been granted, but without grant option" assert expected_error in instance.query_and_get_error( "REVOKE SELECT ON test.table FROM B", user="A" ) assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("GRANT SELECT ON test.* TO A WITH GRANT OPTION") instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == "GRANT SELECT ON test.table TO B\n" instance.query("REVOKE SELECT ON test.table FROM B", user="A") assert instance.query("SHOW GRANTS FOR B") == "" def test_allowed_grantees(): instance.query("CREATE USER A") instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO A WITH GRANT OPTION") instance.query("GRANT SELECT ON test.table TO B", user="A") assert instance.query("SELECT * FROM test.table", user="B") == "1\t5\n2\t10\n" instance.query("REVOKE SELECT ON test.table FROM B", user="A") instance.query("ALTER USER A GRANTEES NONE") expected_error = "user `B` is not allowed as grantee" assert expected_error in instance.query_and_get_error( "GRANT SELECT ON test.table TO B", user="A" ) instance.query("ALTER USER A GRANTEES ANY EXCEPT B") assert ( instance.query("SHOW CREATE USER A") == "CREATE USER A GRANTEES ANY EXCEPT B\n" ) expected_error = "user `B` is not allowed as grantee" assert expected_error in instance.query_and_get_error( "GRANT SELECT ON test.table TO B", user="A" ) instance.query("ALTER USER A GRANTEES B") instance.query("GRANT SELECT ON test.table TO B", user="A") assert instance.query("SELECT * FROM test.table", user="B") == "1\t5\n2\t10\n" instance.query("REVOKE SELECT ON test.table FROM B", user="A") instance.query("ALTER USER A GRANTEES ANY") assert instance.query("SHOW CREATE USER A") == "CREATE USER A\n" instance.query("GRANT SELECT ON test.table TO B", user="A") assert instance.query("SELECT * FROM test.table", user="B") == "1\t5\n2\t10\n" instance.query("ALTER USER A GRANTEES NONE") expected_error = "user `B` is not allowed as grantee" assert expected_error in instance.query_and_get_error( "REVOKE SELECT ON test.table FROM B", user="A" ) instance.query("CREATE USER C GRANTEES ANY EXCEPT C") assert ( instance.query("SHOW CREATE USER C") == "CREATE USER C GRANTEES ANY EXCEPT C\n" ) instance.query("GRANT SELECT ON test.table TO C WITH GRANT OPTION") assert instance.query("SELECT * FROM test.table", user="C") == "1\t5\n2\t10\n" expected_error = "user `C` is not allowed as grantee" assert expected_error in instance.query_and_get_error( "REVOKE SELECT ON test.table FROM C", user="C" ) def test_grant_all_on_table(): instance.query("CREATE USER A, B") instance.query("GRANT ALL ON test.table TO A WITH GRANT OPTION") instance.query("GRANT ALL ON test.table TO B", user="A") assert ( instance.query("SHOW GRANTS FOR B") == "GRANT SHOW TABLES, SHOW COLUMNS, SHOW DICTIONARIES, SELECT, INSERT, ALTER TABLE, ALTER VIEW, CREATE TABLE, CREATE VIEW, CREATE DICTIONARY, " "DROP TABLE, DROP VIEW, DROP DICTIONARY, UNDROP TABLE, TRUNCATE, OPTIMIZE, BACKUP, CREATE ROW POLICY, ALTER ROW POLICY, DROP ROW POLICY, SHOW ROW POLICIES, " "SYSTEM MERGES, SYSTEM TTL MERGES, SYSTEM FETCHES, SYSTEM MOVES, SYSTEM SENDS, SYSTEM REPLICATION QUEUES, SYSTEM DROP REPLICA, SYSTEM SYNC REPLICA, " "SYSTEM RESTART REPLICA, SYSTEM RESTORE REPLICA, SYSTEM WAIT LOADING PARTS, SYSTEM FLUSH DISTRIBUTED, dictGet ON test.table TO B\n" ) instance.query("REVOKE ALL ON test.table FROM B", user="A") assert instance.query("SHOW GRANTS FOR B") == "" def test_implicit_show_grants(): instance.query("CREATE USER A") assert ( instance.query( "select count() FROM system.databases WHERE name='test'", user="A" ) == "0\n" ) assert ( instance.query( "select count() FROM system.tables WHERE database='test' AND name='table'", user="A", ) == "0\n" ) assert ( instance.query( "select count() FROM system.columns WHERE database='test' AND table='table'", user="A", ) == "0\n" ) instance.query("GRANT SELECT(x) ON test.table TO A") assert instance.query("SHOW GRANTS FOR A") == "GRANT SELECT(x) ON test.table TO A\n" assert ( instance.query( "select count() FROM system.databases WHERE name='test'", user="A" ) == "1\n" ) assert ( instance.query( "select count() FROM system.tables WHERE database='test' AND name='table'", user="A", ) == "1\n" ) assert ( instance.query( "select count() FROM system.columns WHERE database='test' AND table='table'", user="A", ) == "1\n" ) instance.query("GRANT SELECT ON test.table TO A") assert instance.query("SHOW GRANTS FOR A") == "GRANT SELECT ON test.table TO A\n" assert ( instance.query( "select count() FROM system.databases WHERE name='test'", user="A" ) == "1\n" ) assert ( instance.query( "select count() FROM system.tables WHERE database='test' AND name='table'", user="A", ) == "1\n" ) assert ( instance.query( "select count() FROM system.columns WHERE database='test' AND table='table'", user="A", ) == "2\n" ) instance.query("GRANT SELECT ON test.* TO A") assert instance.query("SHOW GRANTS FOR A") == "GRANT SELECT ON test.* TO A\n" assert ( instance.query( "select count() FROM system.databases WHERE name='test'", user="A" ) == "1\n" ) assert ( instance.query( "select count() FROM system.tables WHERE database='test' AND name='table'", user="A", ) == "1\n" ) assert ( instance.query( "select count() FROM system.columns WHERE database='test' AND table='table'", user="A", ) == "2\n" ) instance.query("GRANT SELECT ON *.* TO A") assert instance.query("SHOW GRANTS FOR A") == "GRANT SELECT ON *.* TO A\n" assert ( instance.query( "select count() FROM system.databases WHERE name='test'", user="A" ) == "1\n" ) assert ( instance.query( "select count() FROM system.tables WHERE database='test' AND name='table'", user="A", ) == "1\n" ) assert ( instance.query( "select count() FROM system.columns WHERE database='test' AND table='table'", user="A", ) == "2\n" ) instance.query("REVOKE ALL ON *.* FROM A") assert ( instance.query( "select count() FROM system.databases WHERE name='test'", user="A" ) == "0\n" ) assert ( instance.query( "select count() FROM system.tables WHERE database='test' AND name='table'", user="A", ) == "0\n" ) assert ( instance.query( "select count() FROM system.columns WHERE database='test' AND table='table'", user="A", ) == "0\n" ) def test_implicit_create_view_grant(): instance.query("CREATE USER A") expected_error = "Not enough privileges" assert expected_error in instance.query_and_get_error( "CREATE VIEW test.view_1 AS SELECT 1", user="A" ) instance.query("GRANT CREATE TABLE ON test.* TO A") instance.query("CREATE VIEW test.view_1 AS SELECT 1", user="A") assert instance.query("SELECT * FROM test.view_1") == "1\n" instance.query("REVOKE CREATE TABLE ON test.* FROM A") instance.query("DROP TABLE test.view_1") assert expected_error in instance.query_and_get_error( "CREATE VIEW test.view_1 AS SELECT 1", user="A" ) # check grant option instance.query("CREATE USER B") expected_error = "Not enough privileges" assert expected_error in instance.query_and_get_error( "GRANT CREATE VIEW ON test.* TO B", user="A" ) instance.query("GRANT CREATE TABLE ON test.* TO A WITH GRANT OPTION") instance.query("GRANT CREATE VIEW ON test.* TO B", user="A") instance.query("CREATE VIEW test.view_2 AS SELECT 1", user="B") assert instance.query("SELECT * FROM test.view_2") == "1\n" def test_implicit_create_temporary_table_grant(): instance.query("CREATE USER A") expected_error = "Not enough privileges" assert expected_error in instance.query_and_get_error( "CREATE TEMPORARY TABLE tmp(name String)", user="A" ) instance.query("GRANT CREATE TABLE ON test.* TO A") instance.query("CREATE TEMPORARY TABLE tmp(name String)", user="A") instance.query("REVOKE CREATE TABLE ON *.* FROM A") assert expected_error in instance.query_and_get_error( "CREATE TEMPORARY TABLE tmp(name String)", user="A" ) def test_introspection(): instance.query("CREATE USER A") instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO A") instance.query("GRANT CREATE ON *.* TO B WITH GRANT OPTION") assert instance.query("SHOW USERS") == TSV(["A", "B", "default"]) assert instance.query("SHOW CREATE USERS A") == TSV(["CREATE USER A"]) assert instance.query("SHOW CREATE USERS B") == TSV(["CREATE USER B"]) assert instance.query("SHOW CREATE USERS A,B") == TSV( ["CREATE USER A", "CREATE USER B"] ) assert instance.query("SHOW CREATE USERS") == TSV( [ "CREATE USER A", "CREATE USER B", "CREATE USER default IDENTIFIED WITH plaintext_password SETTINGS PROFILE default", ] ) assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT SELECT ON test.table TO A"] ) assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT CREATE ON *.* TO B WITH GRANT OPTION"] ) assert instance.query("SHOW GRANTS FOR default") == TSV( ["GRANT ALL ON *.* TO default WITH GRANT OPTION"] ) assert instance.query("SHOW GRANTS FOR A,B") == TSV( [ "GRANT SELECT ON test.table TO A", "GRANT CREATE ON *.* TO B WITH GRANT OPTION", ] ) assert instance.query("SHOW GRANTS FOR B,A") == TSV( [ "GRANT SELECT ON test.table TO A", "GRANT CREATE ON *.* TO B WITH GRANT OPTION", ] ) assert instance.query("SHOW GRANTS FOR ALL") == TSV( [ "GRANT SELECT ON test.table TO A", "GRANT CREATE ON *.* TO B WITH GRANT OPTION", "GRANT ALL ON *.* TO default WITH GRANT OPTION", ] ) assert instance.query("SHOW GRANTS", user="A") == TSV( ["GRANT SELECT ON test.table TO A"] ) assert instance.query("SHOW GRANTS", user="B") == TSV( ["GRANT CREATE ON *.* TO B WITH GRANT OPTION"] ) assert instance.query("SHOW GRANTS FOR ALL", user="A") == TSV( ["GRANT SELECT ON test.table TO A"] ) assert instance.query("SHOW GRANTS FOR ALL", user="B") == TSV( ["GRANT CREATE ON *.* TO B WITH GRANT OPTION"] ) assert instance.query("SHOW GRANTS FOR ALL") == TSV( [ "GRANT SELECT ON test.table TO A", "GRANT CREATE ON *.* TO B WITH GRANT OPTION", "GRANT ALL ON *.* TO default WITH GRANT OPTION", ] ) expected_error = "necessary to have grant SHOW USERS" assert expected_error in instance.query_and_get_error("SHOW GRANTS FOR B", user="A") expected_access1 = ( "CREATE USER A\n" "CREATE USER B\n" "CREATE USER default IDENTIFIED WITH plaintext_password SETTINGS PROFILE default" ) expected_access2 = ( "GRANT SELECT ON test.table TO A\n" "GRANT CREATE ON *.* TO B WITH GRANT OPTION\n" "GRANT ALL ON *.* TO default WITH GRANT OPTION\n" ) assert expected_access1 in instance.query("SHOW ACCESS") assert expected_access2 in instance.query("SHOW ACCESS") assert instance.query( "SELECT name, storage, auth_type, auth_params, host_ip, host_names, host_names_regexp, host_names_like, default_roles_all, default_roles_list, default_roles_except from system.users WHERE name IN ('A', 'B') ORDER BY name" ) == TSV( [ [ "A", "local directory", "no_password", "{}", "['::/0']", "[]", "[]", "[]", 1, "[]", "[]", ], [ "B", "local directory", "no_password", "{}", "['::/0']", "[]", "[]", "[]", 1, "[]", "[]", ], ] ) assert instance.query( "SELECT * from system.grants WHERE user_name IN ('A', 'B') ORDER BY user_name, access_type, grant_option" ) == TSV( [ ["A", "\\N", "SELECT", "test", "table", "\\N", 0, 0], ["B", "\\N", "CREATE", "\\N", "\\N", "\\N", 0, 1], ] ) def test_current_database(): instance.query("CREATE USER A") instance.query("GRANT SELECT ON table TO A", database="test") assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT SELECT ON test.table TO A"] ) assert instance.query("SHOW GRANTS FOR A", database="test") == TSV( ["GRANT SELECT ON test.table TO A"] ) assert instance.query("SELECT * FROM test.table", user="A") == "1\t5\n2\t10\n" assert ( instance.query("SELECT * FROM table", user="A", database="test") == "1\t5\n2\t10\n" ) instance.query( "CREATE TABLE default.table(x UInt32, y UInt32) ENGINE = MergeTree ORDER BY tuple()" ) assert "Not enough privileges" in instance.query_and_get_error( "SELECT * FROM table", user="A" ) def test_grant_with_replace_option(): instance.query("CREATE USER A") instance.query("GRANT SELECT ON test.table TO A") assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT SELECT ON test.table TO A"] ) instance.query("GRANT INSERT ON test.table TO A WITH REPLACE OPTION") assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT INSERT ON test.table TO A"] ) instance.query("GRANT NONE ON *.* TO A WITH REPLACE OPTION") assert instance.query("SHOW GRANTS FOR A") == TSV([]) instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR A") == TSV([]) assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT SELECT ON test.table TO B"] ) expected_error = ( "it's necessary to have grant INSERT ON test.table WITH GRANT OPTION" ) assert expected_error in instance.query_and_get_error( "GRANT INSERT ON test.table TO B WITH REPLACE OPTION", user="A" ) assert instance.query("SHOW GRANTS FOR A") == TSV([]) assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT SELECT ON test.table TO B"] ) instance.query("GRANT INSERT ON test.table TO A WITH GRANT OPTION") expected_error = ( "it's necessary to have grant SELECT ON test.table WITH GRANT OPTION" ) assert expected_error in instance.query_and_get_error( "GRANT INSERT ON test.table TO B WITH REPLACE OPTION", user="A" ) assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT INSERT ON test.table TO A WITH GRANT OPTION"] ) assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT SELECT ON test.table TO B"] ) instance.query("GRANT SELECT ON test.table TO A WITH GRANT OPTION") instance.query("GRANT INSERT ON test.table TO B WITH REPLACE OPTION", user="A") assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT SELECT, INSERT ON test.table TO A WITH GRANT OPTION"] ) assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT INSERT ON test.table TO B"] ) def test_grant_current_grants(): instance.query("CREATE USER A") instance.query( "GRANT SELECT, CREATE TABLE, CREATE VIEW ON test.* TO A WITH GRANT OPTION" ) assert instance.query("SHOW GRANTS FOR A") == TSV( ["GRANT SELECT, CREATE TABLE, CREATE VIEW ON test.* TO A WITH GRANT OPTION"] ) instance.query("CREATE USER B") instance.query("GRANT CURRENT GRANTS ON *.* TO B", user="A") assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT SELECT, CREATE TABLE, CREATE VIEW ON test.* TO B"] ) instance.query("CREATE USER C") instance.query("GRANT CURRENT GRANTS(CREATE ON test.*) TO C", user="A") assert instance.query("SHOW GRANTS FOR C") == TSV( ["GRANT CREATE TABLE, CREATE VIEW ON test.* TO C"] ) instance.query("DROP USER IF EXISTS C") instance.query("CREATE USER C") instance.query("GRANT CURRENT GRANTS(NONE ON *.*) TO C", user="A") assert instance.query("SHOW GRANTS FOR C") == TSV([]) def test_grant_current_grants_with_partial_revoke(): instance.query("CREATE USER A") instance.query("GRANT CREATE TABLE ON *.* TO A") instance.query("REVOKE CREATE TABLE ON test.* FROM A") instance.query("GRANT CREATE TABLE ON test.table TO A WITH GRANT OPTION") instance.query("GRANT SELECT ON *.* TO A WITH GRANT OPTION") instance.query("REVOKE SELECT ON test.* FROM A") instance.query("GRANT SELECT ON test.table TO A WITH GRANT OPTION") instance.query("GRANT SELECT ON test.table2 TO A") assert instance.query("SHOW GRANTS FOR A") == TSV( [ "GRANT CREATE TABLE ON *.* TO A", "GRANT SELECT ON *.* TO A WITH GRANT OPTION", "REVOKE SELECT, CREATE TABLE ON test.* FROM A", "GRANT SELECT, CREATE TABLE ON test.table TO A WITH GRANT OPTION", "GRANT SELECT ON test.table2 TO A", ] ) instance.query("CREATE USER B") instance.query("GRANT CURRENT GRANTS ON *.* TO B", user="A") assert instance.query("SHOW GRANTS FOR B") == TSV( [ "GRANT SELECT ON *.* TO B", "REVOKE SELECT ON test.* FROM B", "GRANT SELECT, CREATE TABLE ON test.table TO B", ] ) instance.query("DROP USER IF EXISTS B") instance.query("CREATE USER B") instance.query("GRANT CURRENT GRANTS ON *.* TO B WITH GRANT OPTION", user="A") assert instance.query("SHOW GRANTS FOR B") == TSV( [ "GRANT SELECT ON *.* TO B WITH GRANT OPTION", "REVOKE SELECT ON test.* FROM B", "GRANT SELECT, CREATE TABLE ON test.table TO B WITH GRANT OPTION", ] ) instance.query("DROP USER IF EXISTS C") instance.query("CREATE USER C") instance.query("GRANT SELECT ON test.* TO B") instance.query("GRANT CURRENT GRANTS ON *.* TO C", user="B") assert instance.query("SHOW GRANTS FOR C") == TSV( [ "GRANT SELECT ON *.* TO C", "GRANT CREATE TABLE ON test.table TO C", ] ) instance.query("DROP USER IF EXISTS B") instance.query("CREATE USER B") instance.query("GRANT CURRENT GRANTS ON test.* TO B WITH GRANT OPTION", user="A") assert instance.query("SHOW GRANTS FOR B") == TSV( [ "GRANT SELECT, CREATE TABLE ON test.table TO B WITH GRANT OPTION", ] ) def test_current_grants_override(): instance.query("CREATE USER A") instance.query("GRANT SELECT ON *.* TO A WITH GRANT OPTION") instance.query("REVOKE SELECT ON test.* FROM A") assert instance.query("SHOW GRANTS FOR A") == TSV( [ "GRANT SELECT ON *.* TO A WITH GRANT OPTION", "REVOKE SELECT ON test.* FROM A", ] ) instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT SELECT ON test.table TO B"] ) instance.query("GRANT CURRENT GRANTS ON *.* TO B", user="A") assert instance.query("SHOW GRANTS FOR B") == TSV( [ "GRANT SELECT ON *.* TO B", "REVOKE SELECT ON test.* FROM B", "GRANT SELECT ON test.table TO B", ] ) instance.query("DROP USER IF EXISTS B") instance.query("CREATE USER B") instance.query("GRANT SELECT ON test.table TO B") assert instance.query("SHOW GRANTS FOR B") == TSV( ["GRANT SELECT ON test.table TO B"] ) instance.query("GRANT CURRENT GRANTS ON *.* TO B WITH REPLACE OPTION", user="A") assert instance.query("SHOW GRANTS FOR B") == TSV( [ "GRANT SELECT ON *.* TO B", "REVOKE SELECT ON test.* FROM B", ] )