Skip to content

Commit 00e3198

Browse files
authored
Further fix for DENY permissions (#1834)
## Changes - Legacy TACL migration logic currently grouped all permissions in a single `GRANT` statement. Split this into 2 separate statements, one for `DENY` and one for `GRANT` - Added more unit tests, and added integration tests ### Tests <!-- How is this tested? Please see the checklist below and also describe any other relevant tests --> - [x] manually tested - [x] added unit tests - [x] added integration tests - [ ] verified on staging environment (screenshot attached)
1 parent 70d2024 commit 00e3198

File tree

3 files changed

+67
-6
lines changed

3 files changed

+67
-6
lines changed

src/databricks/labs/ucx/hive_metastore/grants.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,15 @@ def hive_grant_sql(self) -> list[str]:
120120
# See https://docs.databricks.com/en/sql/language-manual/security-grant.html
121121
statements = []
122122
actions = self.action_type.split(", ")
123-
if "OWN" in actions:
124-
actions.remove("OWN")
123+
deny_actions = [action for action in actions if "DENIED" in action]
124+
grant_actions = [action for action in actions if "DENIED" not in action]
125+
if "OWN" in grant_actions:
126+
grant_actions.remove("OWN")
125127
statements.append(self._set_owner_sql(object_type, object_key))
126-
if actions:
127-
statements.append(self._apply_grant_sql(", ".join(actions), object_type, object_key))
128+
if grant_actions:
129+
statements.append(self._apply_grant_sql(", ".join(grant_actions), object_type, object_key))
130+
if deny_actions:
131+
statements.append(self._apply_grant_sql(", ".join(deny_actions), object_type, object_key))
128132
return statements
129133

130134
def hive_revoke_sql(self) -> str:

tests/integration/workspace_access/test_tacl.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def test_permission_for_udfs(sql_backend, runtime_ctx, make_group_pair):
212212
sql_backend.execute(f"GRANT SELECT ON FUNCTION {udf_a.full_name} TO `{group.name_in_workspace}`")
213213
sql_backend.execute(f"ALTER FUNCTION {udf_a.full_name} OWNER TO `{group.name_in_workspace}`")
214214
sql_backend.execute(f"GRANT READ_METADATA ON FUNCTION {udf_b.full_name} TO `{group.name_in_workspace}`")
215+
sql_backend.execute(f"DENY `SELECT` ON FUNCTION {udf_b.full_name} TO `{group.name_in_workspace}`")
215216

216217
grants = runtime_ctx.grants_crawler
217218

@@ -222,6 +223,7 @@ def test_permission_for_udfs(sql_backend, runtime_ctx, make_group_pair):
222223
assert f"{group.name_in_workspace}.{udf_a.full_name}:SELECT" in all_initial_grants
223224
assert f"{group.name_in_workspace}.{udf_a.full_name}:OWN" in all_initial_grants
224225
assert f"{group.name_in_workspace}.{udf_b.full_name}:READ_METADATA" in all_initial_grants
226+
assert f"{group.name_in_workspace}.{udf_b.full_name}:DENIED_SELECT" in all_initial_grants
225227

226228
tacl_support = TableAclSupport(grants, sql_backend)
227229
apply_tasks(tacl_support, [group])
@@ -234,7 +236,7 @@ def test_permission_for_udfs(sql_backend, runtime_ctx, make_group_pair):
234236
actual_udf_b_grants = defaultdict(set)
235237
for grant in grants.grants(catalog=schema.catalog_name, database=schema.name, udf=udf_b.name):
236238
actual_udf_b_grants[grant.principal].add(grant.action_type)
237-
assert {"READ_METADATA"} == actual_udf_b_grants[group.name_in_account]
239+
assert {"READ_METADATA", "DENIED_SELECT"} == actual_udf_b_grants[group.name_in_account]
238240

239241

240242
def test_verify_permission_for_udfs(sql_backend, runtime_ctx, make_group):

tests/unit/workspace_access/test_tacl.py

+56-1
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def test_tacl_udf_applier(mocker):
391391
assert validation_res
392392

393393

394-
def test_tacl_applier_multiple_actions(mocker):
394+
def test_tacl_applier_multiple_actions():
395395
sql_backend = MockBackend(
396396
rows={
397397
"SELECT \\* FROM hive_metastore.test.grants": UCX_GRANTS[
@@ -444,6 +444,61 @@ def test_tacl_applier_multiple_actions(mocker):
444444
assert validation_res
445445

446446

447+
def test_tacl_applier_deny_and_grant():
448+
sql_backend = MockBackend(
449+
rows={
450+
"SELECT \\* FROM hive_metastore.test.grants": UCX_GRANTS[
451+
("abc", "SELECT", "catalog_a", "database_b", "table_c", None, None, False, False)
452+
],
453+
"SHOW GRANTS ON TABLE catalog_a.database_b.table_c": SHOW_GRANTS[
454+
("account-abc", "READ_METADATA", "TABLE", "table_c"),
455+
("account-abc", "SELECT", "TABLE", "table_c"),
456+
("account-abc", "DENIED_MODIFY", "TABLE", "table_c"),
457+
],
458+
}
459+
)
460+
tables_crawler = TablesCrawler(sql_backend, "test")
461+
udf_crawler = UdfsCrawler(sql_backend, "test")
462+
grants_crawler = GrantsCrawler(tables_crawler, udf_crawler)
463+
table_acl_support = TableAclSupport(grants_crawler, sql_backend)
464+
465+
permissions = Permissions(
466+
object_type="TABLE",
467+
object_id="catalog_a.database_b.table_c",
468+
raw=json.dumps(
469+
{
470+
"principal": "abc",
471+
"action_type": "SELECT, READ_METADATA, DENIED_MODIFY",
472+
"catalog": "catalog_a",
473+
"database": "database_b",
474+
"table": "table_c",
475+
}
476+
),
477+
)
478+
grp = [
479+
MigratedGroup(
480+
id_in_workspace=None,
481+
name_in_workspace="abc",
482+
name_in_account="account-abc",
483+
temporary_name="tmp-backup-abc",
484+
members=None,
485+
entitlements=None,
486+
external_id=None,
487+
roles=None,
488+
)
489+
]
490+
migration_state = MigrationState(grp)
491+
task = table_acl_support.get_apply_task(permissions, migration_state)
492+
validation_res = task()
493+
494+
assert [
495+
"GRANT SELECT, READ_METADATA ON TABLE catalog_a.database_b.table_c TO `account-abc`",
496+
"DENY `MODIFY` ON TABLE catalog_a.database_b.table_c TO `account-abc`",
497+
"SHOW GRANTS ON TABLE catalog_a.database_b.table_c",
498+
] == sql_backend.queries
499+
assert validation_res
500+
501+
447502
def test_tacl_applier_no_target_principal(mocker):
448503
sql_backend = MockBackend()
449504
table_acl_support = TableAclSupport(mocker.Mock(), sql_backend)

0 commit comments

Comments
 (0)