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 dda4328177 [#9180] fix(spark-connector): Override tableExists() to 
bypass authorization check (#9183)
dda4328177 is described below

commit dda4328177e5060d537e103a5f32b333fe2c0119
Author: Bharath Krishna <[email protected]>
AuthorDate: Thu Nov 20 18:34:39 2025 -0800

    [#9180] fix(spark-connector): Override tableExists() to bypass 
authorization check (#9183)
    
    ### What changes were proposed in this pull request?
    
    Override tableExists() in BaseCatalog to catch ForbiddenException when
    users lack LOAD_TABLE privilege. Returns false to allow CREATE TABLE IF
    NOT EXISTS to proceed.
    
    ### Why are the changes needed?
    Users with only CREATE_TABLE privilege couldn't execute CREATE TABLE IF
    NOT EXISTS because tableExists() internally calls loadTable(), which
    requires LOAD_TABLE privilege.
    
    Fix: #9180
    
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. Users with CREATE_TABLE privilege can now execute CREATE TABLE IF
    NOT EXISTS without needing LOAD_TABLE privilege.
    
    
    ### How was this patch tested?
    Added integration test
---
 .../spark/connector/catalog/BaseCatalog.java       | 19 ++++++++++
 .../test/authorization/SparkAuthorizationIT.java   | 44 ++++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git 
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/catalog/BaseCatalog.java
 
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/catalog/BaseCatalog.java
index 5706895caa..a763c990d9 100644
--- 
a/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/catalog/BaseCatalog.java
+++ 
b/spark-connector/spark-common/src/main/java/org/apache/gravitino/spark/connector/catalog/BaseCatalog.java
@@ -30,6 +30,7 @@ import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.SchemaChange;
+import org.apache.gravitino.exceptions.ForbiddenException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NonEmptySchemaException;
 import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
@@ -297,6 +298,24 @@ public abstract class BaseCatalog implements TableCatalog, 
SupportsNamespaces {
         .purgeTable(NameIdentifier.of(getDatabase(ident), ident.name()));
   }
 
+  @Override
+  public boolean tableExists(Identifier ident) {
+    // Gravitino uses loadTable() to verify table existence, which requires 
LOAD_TABLE privilege.
+    // For CREATE TABLE IF NOT EXISTS operations, users may only have 
CREATE_TABLE privilege.
+    // When ForbiddenException is thrown (lacking LOAD_TABLE privilege), we 
return false to allow
+    // the CREATE TABLE operation to proceed.
+    // See: https://github.com/apache/gravitino/issues/9180
+    try {
+      loadGravitinoTable(ident);
+      return true;
+    } catch (NoSuchTableException e) {
+      return false;
+    } catch (ForbiddenException e) {
+      // User lacks LOAD_TABLE privilege, return false to allow CREATE TABLE 
IF NOT EXISTS
+      return false;
+    }
+  }
+
   @Override
   public void renameTable(Identifier oldIdent, Identifier newIdent)
       throws NoSuchTableException, TableAlreadyExistsException {
diff --git 
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/authorization/SparkAuthorizationIT.java
 
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/authorization/SparkAuthorizationIT.java
index d2ccf83289..e91fc4b77f 100644
--- 
a/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/authorization/SparkAuthorizationIT.java
+++ 
b/spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/authorization/SparkAuthorizationIT.java
@@ -269,6 +269,50 @@ public abstract class SparkAuthorizationIT extends BaseIT {
     normalUserSparkSession.sql("DROP TABLE table_a");
   }
 
+  @Test
+  @Order(5)
+  public void testCreateTableIfNotExistsWithoutLoadTablePrivilege() {
+    GravitinoMetalake gravitinoMetalake = client.loadMetalake(METALAKE);
+    String testTable = "test_if_not_exists";
+    String testRole = "role_no_select";
+
+    // Temporarily revoke ROLE from user to avoid interference from test 2's 
CreateTable.deny()
+    // ROLE has deny from test 2, which would override any allow privileges in 
testRole
+    gravitinoMetalake.revokeRolesFromUser(ImmutableList.of(ROLE), NORMAL_USER);
+
+    // Create role with CREATE_TABLE but without SELECT_TABLE privilege
+    SecurableObject catalogObject =
+        SecurableObjects.ofCatalog(JDBC_CATALOG, 
ImmutableList.of(Privileges.UseCatalog.allow()));
+    SecurableObject schemaObject =
+        SecurableObjects.ofSchema(
+            catalogObject,
+            JDBC_DATABASE,
+            ImmutableList.of(Privileges.UseSchema.allow(), 
Privileges.CreateTable.allow()));
+    gravitinoMetalake.createRole(
+        testRole, new HashMap<>(), ImmutableList.of(catalogObject, 
schemaObject));
+    gravitinoMetalake.grantRolesToUser(ImmutableList.of(testRole), 
NORMAL_USER);
+
+    try {
+      normalUserSparkSession.sql(
+          String.format("CREATE TABLE IF NOT EXISTS %s (id INT, name STRING)", 
testTable));
+
+      TableCatalog tableCatalog = 
gravitinoMetalake.loadCatalog(JDBC_CATALOG).asTableCatalog();
+      boolean tableExists = 
tableCatalog.tableExists(NameIdentifier.of(JDBC_DATABASE, testTable));
+      Assertions.assertTrue(tableExists, "Table should have been created");
+
+      normalUserSparkSession.sql(
+          String.format("CREATE TABLE IF NOT EXISTS %s (id INT, name STRING)", 
testTable));
+
+      // Clean up
+      tableCatalog.dropTable(NameIdentifier.of(JDBC_DATABASE, testTable));
+    } finally {
+      // Clean up test role and restore ROLE to user for subsequent tests
+      gravitinoMetalake.revokeRolesFromUser(ImmutableList.of(testRole), 
NORMAL_USER);
+      gravitinoMetalake.deleteRole(testRole);
+      gravitinoMetalake.grantRolesToUser(ImmutableList.of(ROLE), NORMAL_USER);
+    }
+  }
+
   private void assertEqualsRows(List<Row> exceptRows, List<Row> actualRows) {
     Assertions.assertEquals(exceptRows.size(), actualRows.size());
     for (int i = 0; i < exceptRows.size(); i++) {

Reply via email to