This is an automated email from the ASF dual-hosted git repository.

yuqi1129 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 06a5a4f323 [#13174] fix(core): Keep plugin privileges when the 
external drop returns false (#13196)
06a5a4f323 is described below

commit 06a5a4f323764041bb33974675516ca8b83afece
Author: Qi Yu <[email protected]>
AuthorDate: Wed Sep 16 19:32:47 2026 +0800

    [#13174] fix(core): Keep plugin privileges when the external drop returns 
false (#13196)
    
    ### What changes were proposed in this pull request?
    
    `TableHookDispatcher.dropTable`/`purgeTable`,
    `SchemaHookDispatcher.dropSchema`, `FilesetHookDispatcher.dropFileset`
    and `TopicHookDispatcher.dropTopic` now call
    `AuthorizationUtils.authorizationPluginRemovePrivileges` only when the
    inner dispatcher returned `true`. Adds one test per hook that verifies a
    `false` result leaves the plugin untouched.
    
    ### Why are the changes needed?
    
    #12232 made the dispatchers keep the store registration when the
    external drop returns `false` (the object was renamed or dropped out of
    band), but the hook layer still removed the authorization-plugin
    privileges unconditionally. Under a concurrent rename × drop the entity
    alive under its new name lost its Ranger privileges while keeping its
    Gravitino registration. `FunctionHookDispatcher` already had the guard;
    this aligns the other four.
    
    Fix: #13174
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    New unit tests `testDrop*KeepsPrivilegesWhen*ReturnsFalse` in the four
    `Test*HookDispatcher` classes; all `*HookDispatcher` tests pass
    (`./gradlew :core:test --tests '*HookDispatcher' -PskipITs`).
---
 .../ranger/integration/test/RangerBaseE2EIT.java   | 20 +++++++++++++---
 .../gravitino/hook/FilesetHookDispatcher.java      |  7 ++++--
 .../gravitino/hook/SchemaHookDispatcher.java       |  8 +++++--
 .../apache/gravitino/hook/TableHookDispatcher.java | 14 +++++++----
 .../apache/gravitino/hook/TopicHookDispatcher.java |  7 ++++--
 .../gravitino/hook/TestFilesetHookDispatcher.java  | 23 ++++++++++++++++++
 .../gravitino/hook/TestSchemaHookDispatcher.java   | 22 +++++++++++++++++
 .../gravitino/hook/TestTableHookDispatcher.java    | 28 ++++++++++++++++++++++
 .../gravitino/hook/TestTopicHookDispatcher.java    | 23 ++++++++++++++++++
 9 files changed, 139 insertions(+), 13 deletions(-)

diff --git 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
index 3c38ba0908..e404e77a36 100644
--- 
a/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
+++ 
b/authorizations/authorization-ranger/src/test/java/org/apache/gravitino/authorization/ranger/integration/test/RangerBaseE2EIT.java
@@ -1002,8 +1002,16 @@ public abstract class RangerBaseE2EIT extends BaseIT {
     // Owner has all the privileges except for creating table
     checkTableAllPrivilegesExceptForCreating();
 
-    // Delete Gravitino's meta data
-    catalog.asTableCatalog().purgeTable(NameIdentifier.of(schemaName, 
tableName));
+    // The table was dropped externally, so Gravitino keeps its registration 
and privileges.
+    Assertions.assertFalse(
+        catalog.asTableCatalog().purgeTable(NameIdentifier.of(schemaName, 
tableName)));
+    waitForUpdatingPolicies();
+
+    // The retained owner privileges allow recreating the same table. A 
successful Gravitino purge
+    // then removes both its registration and privileges.
+    sparkSession.sql(SQL_CREATE_TABLE);
+    Assertions.assertTrue(
+        catalog.asTableCatalog().purgeTable(NameIdentifier.of(schemaName, 
tableName)));
     waitForUpdatingPolicies();
 
     checker.checkCreateTable();
@@ -1022,7 +1030,13 @@ public abstract class RangerBaseE2EIT extends BaseIT {
 
     // Succeed to drop schema
     sparkSession.sql(SQL_DROP_SCHEMA);
-    catalog.asSchemas().dropSchema(schemaName, false);
+    Assertions.assertFalse(catalog.asSchemas().dropSchema(schemaName, false));
+    waitForUpdatingPolicies();
+
+    // The retained owner privileges allow recreating the same schema. A 
successful Gravitino drop
+    // then removes both its registration and privileges.
+    sparkSession.sql(SQL_CREATE_SCHEMA);
+    Assertions.assertTrue(catalog.asSchemas().dropSchema(schemaName, false));
     waitForUpdatingPolicies();
 
     checker.checkCreateSchema();
diff --git 
a/core/src/main/java/org/apache/gravitino/hook/FilesetHookDispatcher.java 
b/core/src/main/java/org/apache/gravitino/hook/FilesetHookDispatcher.java
index 0e61cf758f..199e8010ab 100644
--- a/core/src/main/java/org/apache/gravitino/hook/FilesetHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/FilesetHookDispatcher.java
@@ -118,8 +118,11 @@ public class FilesetHookDispatcher implements 
FilesetDispatcher {
     List<String> locations =
         AuthorizationUtils.getMetadataObjectLocation(ident, 
Entity.EntityType.FILESET);
     boolean dropped = dispatcher.dropFileset(ident);
-    AuthorizationUtils.authorizationPluginRemovePrivileges(
-        ident, Entity.EntityType.FILESET, locations);
+    // A false result means the fileset was already gone and the registration 
was kept.
+    if (dropped) {
+      AuthorizationUtils.authorizationPluginRemovePrivileges(
+          ident, Entity.EntityType.FILESET, locations);
+    }
     return dropped;
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/hook/SchemaHookDispatcher.java 
b/core/src/main/java/org/apache/gravitino/hook/SchemaHookDispatcher.java
index 7a79a9d213..8d2ce75b7b 100644
--- a/core/src/main/java/org/apache/gravitino/hook/SchemaHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/SchemaHookDispatcher.java
@@ -176,8 +176,12 @@ public class SchemaHookDispatcher implements 
SchemaDispatcher {
     List<String> locations =
         AuthorizationUtils.getMetadataObjectLocation(ident, 
Entity.EntityType.SCHEMA);
     boolean dropped = dispatcher.dropSchema(ident, cascade);
-    AuthorizationUtils.authorizationPluginRemovePrivileges(
-        ident, Entity.EntityType.SCHEMA, locations);
+    // Only a schema that was really dropped loses its privileges; a false 
result means the
+    // registration was kept.
+    if (dropped) {
+      AuthorizationUtils.authorizationPluginRemovePrivileges(
+          ident, Entity.EntityType.SCHEMA, locations);
+    }
     return dropped;
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java 
b/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
index 9a1037e1ca..768c1e5410 100644
--- a/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
@@ -129,8 +129,12 @@ public class TableHookDispatcher implements 
TableDispatcher {
     List<String> locations =
         AuthorizationUtils.getMetadataObjectLocation(ident, 
Entity.EntityType.TABLE);
     boolean dropped = dispatcher.dropTable(ident);
-    AuthorizationUtils.authorizationPluginRemovePrivileges(
-        ident, Entity.EntityType.TABLE, locations);
+    // A false result means the external table was renamed or dropped out of 
band and the
+    // registration was kept; the entity alive under another name must keep 
its privileges too.
+    if (dropped) {
+      AuthorizationUtils.authorizationPluginRemovePrivileges(
+          ident, Entity.EntityType.TABLE, locations);
+    }
     return dropped;
   }
 
@@ -139,8 +143,10 @@ public class TableHookDispatcher implements 
TableDispatcher {
     List<String> locations =
         AuthorizationUtils.getMetadataObjectLocation(ident, 
Entity.EntityType.TABLE);
     boolean purged = dispatcher.purgeTable(ident);
-    AuthorizationUtils.authorizationPluginRemovePrivileges(
-        ident, Entity.EntityType.TABLE, locations);
+    if (purged) {
+      AuthorizationUtils.authorizationPluginRemovePrivileges(
+          ident, Entity.EntityType.TABLE, locations);
+    }
     return purged;
   }
 
diff --git 
a/core/src/main/java/org/apache/gravitino/hook/TopicHookDispatcher.java 
b/core/src/main/java/org/apache/gravitino/hook/TopicHookDispatcher.java
index ad8440942b..4840cd87ae 100644
--- a/core/src/main/java/org/apache/gravitino/hook/TopicHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/TopicHookDispatcher.java
@@ -88,8 +88,11 @@ public class TopicHookDispatcher implements TopicDispatcher {
     List<String> locations =
         AuthorizationUtils.getMetadataObjectLocation(ident, 
Entity.EntityType.TOPIC);
     boolean dropped = dispatcher.dropTopic(ident);
-    AuthorizationUtils.authorizationPluginRemovePrivileges(
-        ident, Entity.EntityType.TOPIC, locations);
+    // A false result means the topic was dropped out of band and the 
registration was kept.
+    if (dropped) {
+      AuthorizationUtils.authorizationPluginRemovePrivileges(
+          ident, Entity.EntityType.TOPIC, locations);
+    }
     return dropped;
   }
 
diff --git 
a/core/src/test/java/org/apache/gravitino/hook/TestFilesetHookDispatcher.java 
b/core/src/test/java/org/apache/gravitino/hook/TestFilesetHookDispatcher.java
index a4431e7cbf..ae662040b3 100644
--- 
a/core/src/test/java/org/apache/gravitino/hook/TestFilesetHookDispatcher.java
+++ 
b/core/src/test/java/org/apache/gravitino/hook/TestFilesetHookDispatcher.java
@@ -40,6 +40,7 @@ import static 
org.apache.gravitino.Configs.VERSION_RETENTION_COUNT;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import java.util.Map;
@@ -50,6 +51,7 @@ import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.authorization.AccessControlManager;
+import org.apache.gravitino.authorization.AuthorizationUtils;
 import org.apache.gravitino.authorization.Owner;
 import org.apache.gravitino.authorization.OwnerDispatcher;
 import org.apache.gravitino.catalog.CatalogManager;
@@ -69,6 +71,7 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
 public class TestFilesetHookDispatcher extends TestOperationDispatcher {
@@ -190,6 +193,26 @@ public class TestFilesetHookDispatcher extends 
TestOperationDispatcher {
     }
   }
 
+  @Test
+  public void testDropKeepsPrivilegesWhenDropReturnsFalse() {
+    FilesetDispatcher dispatcher = Mockito.mock(FilesetDispatcher.class);
+    FilesetHookDispatcher hook = new FilesetHookDispatcher(dispatcher);
+    NameIdentifier ident = NameIdentifier.of(metalake, catalog, "schema", 
"fileset");
+    Mockito.when(dispatcher.dropFileset(ident)).thenReturn(false);
+
+    try (MockedStatic<AuthorizationUtils> authz = 
Mockito.mockStatic(AuthorizationUtils.class)) {
+      authz
+          .when(() -> AuthorizationUtils.getMetadataObjectLocation(any(), 
any()))
+          .thenReturn(ImmutableList.of("/test"));
+
+      Assertions.assertFalse(hook.dropFileset(ident));
+
+      authz.verify(
+          () -> AuthorizationUtils.authorizationPluginRemovePrivileges(any(), 
any(), any()),
+          Mockito.never());
+    }
+  }
+
   @Test
   public void testDropAuthorizationPrivilege() {
     Namespace filesetNs = Namespace.of(metalake, catalog, "schema11212");
diff --git 
a/core/src/test/java/org/apache/gravitino/hook/TestSchemaHookDispatcher.java 
b/core/src/test/java/org/apache/gravitino/hook/TestSchemaHookDispatcher.java
index fbf0f43949..478ab3efd5 100644
--- a/core/src/test/java/org/apache/gravitino/hook/TestSchemaHookDispatcher.java
+++ b/core/src/test/java/org/apache/gravitino/hook/TestSchemaHookDispatcher.java
@@ -213,6 +213,28 @@ public class TestSchemaHookDispatcher {
         "Pre-existing ancestor 'A' must keep its owner; only newly-created 
schemas are claimed");
   }
 
+  @Test
+  public void testDropSchemaKeepsPrivilegesWhenExternalDropReturnsFalse() {
+    NameIdentifier ident = NameIdentifier.of("test_metalake", "test_catalog", 
"A:B:C");
+    when(mockDispatcher.dropSchema(eq(ident), eq(false))).thenReturn(false);
+
+    try (MockedStatic<AuthorizationUtils> authz = 
Mockito.mockStatic(AuthorizationUtils.class)) {
+      authz
+          .when(
+              () ->
+                  AuthorizationUtils.getMetadataObjectLocation(
+                      any(NameIdentifier.class), any(Entity.EntityType.class)))
+          .thenReturn(ImmutableList.of("/test"));
+
+      Assertions.assertFalse(hookDispatcher.dropSchema(ident, false));
+
+      // Nothing was dropped, so the schema that is still registered keeps its 
privileges.
+      authz.verify(
+          () -> AuthorizationUtils.authorizationPluginRemovePrivileges(any(), 
any(), any()),
+          never());
+    }
+  }
+
   @Test
   public void testDropSchemaRemovesPrivileges() {
     NameIdentifier ident = NameIdentifier.of("test_metalake", "test_catalog", 
"A:B:C");
diff --git 
a/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java 
b/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
index b1a61795a7..befd3a9dd4 100644
--- a/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
+++ b/core/src/test/java/org/apache/gravitino/hook/TestTableHookDispatcher.java
@@ -48,6 +48,7 @@ import 
org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.sorts.SortOrder;
 import org.apache.gravitino.rel.expressions.transforms.Transform;
 import org.apache.gravitino.rel.indexes.Index;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.MockedStatic;
@@ -81,6 +82,33 @@ public class TestTableHookDispatcher {
     }
   }
 
+  @Test
+  public void testDropKeepsPrivilegesWhenExternalDropReturnsFalse() {
+    TableDispatcher dispatcher = Mockito.mock(TableDispatcher.class);
+    TableHookDispatcher hook = new TableHookDispatcher(dispatcher, () -> null);
+    NameIdentifier ident = NameIdentifier.of(METALAKE, CATALOG, "schema", 
"table");
+    // false means the table was renamed or dropped out of band: the 
registration is kept, so the
+    // plugin privileges of the entity that is still alive under another name 
must be kept too.
+    Mockito.when(dispatcher.dropTable(ident)).thenReturn(false);
+    Mockito.when(dispatcher.purgeTable(ident)).thenReturn(false);
+
+    try (MockedStatic<AuthorizationUtils> authorizationUtils =
+        Mockito.mockStatic(AuthorizationUtils.class)) {
+      authorizationUtils
+          .when(() -> AuthorizationUtils.getMetadataObjectLocation(ident, 
Entity.EntityType.TABLE))
+          .thenReturn(ImmutableList.of("/test"));
+
+      Assertions.assertFalse(hook.dropTable(ident));
+      Assertions.assertFalse(hook.purgeTable(ident));
+
+      authorizationUtils.verify(
+          () ->
+              AuthorizationUtils.authorizationPluginRemovePrivileges(
+                  Mockito.any(), Mockito.any(), Mockito.any()),
+          Mockito.never());
+    }
+  }
+
   @Test
   public void testCreateTableSetsOwnerWithNormalizedIdentifier() throws 
Exception {
     CatalogManager catalogManager = Mockito.mock(CatalogManager.class);
diff --git 
a/core/src/test/java/org/apache/gravitino/hook/TestTopicHookDispatcher.java 
b/core/src/test/java/org/apache/gravitino/hook/TestTopicHookDispatcher.java
index e4294b2c17..96c851aff7 100644
--- a/core/src/test/java/org/apache/gravitino/hook/TestTopicHookDispatcher.java
+++ b/core/src/test/java/org/apache/gravitino/hook/TestTopicHookDispatcher.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.hook;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import org.apache.commons.lang3.reflect.FieldUtils;
@@ -29,6 +30,7 @@ import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.authorization.AccessControlManager;
+import org.apache.gravitino.authorization.AuthorizationUtils;
 import org.apache.gravitino.authorization.Owner;
 import org.apache.gravitino.authorization.OwnerDispatcher;
 import org.apache.gravitino.catalog.CatalogManager;
@@ -46,6 +48,7 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 
 public class TestTopicHookDispatcher extends TestOperationDispatcher {
@@ -156,6 +159,26 @@ public class TestTopicHookDispatcher extends 
TestOperationDispatcher {
     }
   }
 
+  @Test
+  public void testDropKeepsPrivilegesWhenExternalDropReturnsFalse() {
+    TopicDispatcher dispatcher = Mockito.mock(TopicDispatcher.class);
+    TopicHookDispatcher hook = new TopicHookDispatcher(dispatcher);
+    NameIdentifier ident = NameIdentifier.of(metalake, catalog, "schema", 
"topic");
+    Mockito.when(dispatcher.dropTopic(ident)).thenReturn(false);
+
+    try (MockedStatic<AuthorizationUtils> authz = 
Mockito.mockStatic(AuthorizationUtils.class)) {
+      authz
+          .when(() -> AuthorizationUtils.getMetadataObjectLocation(any(), 
any()))
+          .thenReturn(ImmutableList.of("/test"));
+
+      Assertions.assertFalse(hook.dropTopic(ident));
+
+      authz.verify(
+          () -> AuthorizationUtils.authorizationPluginRemovePrivileges(any(), 
any(), any()),
+          Mockito.never());
+    }
+  }
+
   @Test
   public void testDropAuthorizationPrivilege() {
     Namespace topicNs = Namespace.of(metalake, catalog, "schema1123");

Reply via email to