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++) {