This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new b03f4229ab [#9747][followup]feat(authz): Add view auth to
IcebergRESTAuthInterceptionService (#10020)
b03f4229ab is described below
commit b03f4229ab3a6bce1ba7d7df2b9caaf272a7804f
Author: Bharath Krishna <[email protected]>
AuthorDate: Mon Feb 23 11:32:17 2026 +0530
[#9747][followup]feat(authz): Add view auth to
IcebergRESTAuthInterceptionService (#10020)
### What changes were proposed in this pull request?
Add view authz classes to IcebergRESTAuthInterceptionService.
Also update the ICEBERG_LOAD_VIEW_AUTHORIZATION_EXPRESSION to have
ANY_SELECT_TABLE / ANY_MODIFY_TABLE / ANY_CREATE_TABLE
### Why are the changes needed?
This is to enable authorization for views
Fix: #9747
### Does this PR introduce _any_ user-facing change?
No
### How was this patch tested?
Update integration tests
---
.../iceberg/service/rest/IcebergViewOperations.java | 10 ++--------
.../IcebergMetadataAuthorizationMethodInterceptor.java | 10 ++++++++--
.../web/filter/IcebergRESTAuthInterceptionService.java | 6 +++++-
.../iceberg/integration/test/IcebergViewAuthorizationIT.java | 10 +++++-----
.../expression/AuthorizationExpressionConstants.java | 12 ++++++++++++
5 files changed, 32 insertions(+), 16 deletions(-)
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergViewOperations.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergViewOperations.java
index 5a1adc9f53..86e3d66b5a 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergViewOperations.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/iceberg/service/rest/IcebergViewOperations.java
@@ -162,10 +162,7 @@ public class IcebergViewOperations {
@Timed(name = "load-view." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
@ResponseMetered(name = "load-view", absolute = true)
@AuthorizationExpression(
- expression =
- "ANY(OWNER, METALAKE, CATALOG) || "
- + "SCHEMA_OWNER_WITH_USE_CATALOG || "
- + "ANY_USE_CATALOG && ANY_USE_SCHEMA && (VIEW::OWNER ||
ANY_SELECT_VIEW || ANY_CREATE_VIEW)",
+ expression =
AuthorizationExpressionConstants.ICEBERG_LOAD_VIEW_AUTHORIZATION_EXPRESSION,
accessMetadataType = MetadataObject.Type.VIEW)
public Response loadView(
@AuthorizationMetadata(type = Entity.EntityType.CATALOG)
@PathParam("prefix") String prefix,
@@ -283,10 +280,7 @@ public class IcebergViewOperations {
@Timed(name = "view-exists." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
@ResponseMetered(name = "view-exists", absolute = true)
@AuthorizationExpression(
- expression =
- "ANY(OWNER, METALAKE, CATALOG) || "
- + "SCHEMA_OWNER_WITH_USE_CATALOG || "
- + "ANY_USE_CATALOG && ANY_USE_SCHEMA && (VIEW::OWNER ||
ANY_SELECT_VIEW || ANY_CREATE_VIEW)",
+ expression =
AuthorizationExpressionConstants.ICEBERG_LOAD_VIEW_AUTHORIZATION_EXPRESSION,
accessMetadataType = MetadataObject.Type.VIEW)
public Response viewExists(
@AuthorizationMetadata(type = Entity.EntityType.CATALOG)
@PathParam("prefix") String prefix,
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergMetadataAuthorizationMethodInterceptor.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergMetadataAuthorizationMethodInterceptor.java
index 3c751a5065..643e8eb532 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergMetadataAuthorizationMethodInterceptor.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergMetadataAuthorizationMethodInterceptor.java
@@ -81,10 +81,16 @@ public class IcebergMetadataAuthorizationMethodInterceptor
metalakeName, catalog, schema,
RESTUtil.decodeString(value)));
break;
case VIEW:
+ String decodedViewName = RESTUtil.decodeString(value);
nameIdentifierMap.put(
EntityType.VIEW,
- NameIdentifierUtil.ofView(
- metalakeName, catalog, schema,
RESTUtil.decodeString(value)));
+ NameIdentifierUtil.ofView(metalakeName, catalog, schema,
decodedViewName));
+ // Also register as TABLE so ANY_SELECT_TABLE in
+ // ICEBERG_LOAD_VIEW_AUTHORIZATION_EXPRESSION
+ // matches when Spark probes viewExists(tableName) during table
resolution.
+ nameIdentifierMap.put(
+ EntityType.TABLE,
+ NameIdentifierUtil.ofTable(metalakeName, catalog, schema,
decodedViewName));
break;
default:
break;
diff --git
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergRESTAuthInterceptionService.java
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergRESTAuthInterceptionService.java
index e1f4f6005b..b7ea32d820 100644
---
a/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergRESTAuthInterceptionService.java
+++
b/iceberg/iceberg-rest-server/src/main/java/org/apache/gravitino/server/web/filter/IcebergRESTAuthInterceptionService.java
@@ -26,6 +26,8 @@ import org.aopalliance.intercept.MethodInterceptor;
import org.apache.gravitino.iceberg.service.rest.IcebergNamespaceOperations;
import org.apache.gravitino.iceberg.service.rest.IcebergTableOperations;
import org.apache.gravitino.iceberg.service.rest.IcebergTableRenameOperations;
+import org.apache.gravitino.iceberg.service.rest.IcebergViewOperations;
+import org.apache.gravitino.iceberg.service.rest.IcebergViewRenameOperations;
import org.glassfish.hk2.api.Filter;
/**
@@ -41,7 +43,9 @@ public class IcebergRESTAuthInterceptionService extends
BaseInterceptionService
ImmutableSet.of(
IcebergTableOperations.class.getName(),
IcebergTableRenameOperations.class.getName(),
- IcebergNamespaceOperations.class.getName()));
+ IcebergNamespaceOperations.class.getName(),
+ IcebergViewOperations.class.getName(),
+ IcebergViewRenameOperations.class.getName()));
}
@Override
diff --git
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergViewAuthorizationIT.java
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergViewAuthorizationIT.java
index 73532b49b3..827647020e 100644
---
a/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergViewAuthorizationIT.java
+++
b/iceberg/iceberg-rest-server/src/test/java/org/apache/gravitino/iceberg/integration/test/IcebergViewAuthorizationIT.java
@@ -70,8 +70,8 @@ public class IcebergViewAuthorizationIT extends
IcebergAuthorizationIT {
MetadataObjects.of(
Arrays.asList(GRAVITINO_CATALOG_NAME, SCHEMA_NAME),
MetadataObject.Type.SCHEMA);
metalakeClientWithAllPrivilege.setOwner(schemaObject, SUPER_USER,
Owner.Type.USER);
- clearViews();
grantUseSchemaRole(SCHEMA_NAME);
+ clearViews();
sql("USE %s;", SPARK_CATALOG_NAME);
sql("USE %s;", SCHEMA_NAME);
}
@@ -309,10 +309,8 @@ public class IcebergViewAuthorizationIT extends
IcebergAuthorizationIT {
Assertions.assertTrue(owner.isPresent());
Assertions.assertEquals(NORMAL_USER, owner.get().name());
- // Clean up
- revokeRole(createViewRole);
- // NORMAL_USER still owns the renamed view, so they can drop it
sql("DROP VIEW IF EXISTS %s.%s", destSchema, viewName + "_renamed");
+ revokeRole(createViewRole);
catalogClientWithAllPrivilege.asSchemas().dropSchema(destSchema, false);
}
@@ -463,7 +461,9 @@ public class IcebergViewAuthorizationIT extends
IcebergAuthorizationIT {
securableObjects.add(catalogObject);
SecurableObject schemaObject =
SecurableObjects.ofSchema(
- catalogObject, schema,
ImmutableList.of(Privileges.CreateView.allow()));
+ catalogObject,
+ schema,
+ ImmutableList.of(Privileges.UseSchema.allow(),
Privileges.CreateView.allow()));
securableObjects.add(schemaObject);
metalakeClientWithAllPrivilege.createRole(roleName, new HashMap<>(),
securableObjects);
metalakeClientWithAllPrivilege.grantRolesToUser(ImmutableList.of(roleName),
NORMAL_USER);
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
index 188b24683c..d145b2c9b8 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
@@ -82,6 +82,18 @@ public class AuthorizationExpressionConstants {
ANY_USE_CATALOG && ANY_USE_SCHEMA && (VIEW::OWNER ||
ANY_SELECT_VIEW || ANY_CREATE_VIEW)
""";
+ // Adding ANY_SELECT_TABLE / ANY_MODIFY_TABLE / ANY_CREATE_TABLE here
because Spark probes
+ // viewExists(tableName) when resolving any relation. Without table
privileges in the
+ // expression, users who only hold table grants get a spurious 403 on the
HEAD /views/{name}
+ // probe, blocking legitimate table reads.
+ public static final String ICEBERG_LOAD_VIEW_AUTHORIZATION_EXPRESSION =
+ """
+ ANY(OWNER, METALAKE, CATALOG) ||
+ SCHEMA_OWNER_WITH_USE_CATALOG ||
+ ANY_USE_CATALOG && ANY_USE_SCHEMA && (VIEW::OWNER ||
ANY_SELECT_VIEW || ANY_CREATE_VIEW
+ || ANY_SELECT_TABLE || ANY_MODIFY_TABLE ||
ANY_CREATE_TABLE)
+ """;
+
public static final String FILTER_TABLE_AUTHORIZATION_EXPRESSION =
"""
ANY(OWNER, METALAKE, CATALOG, SCHEMA, TABLE) ||