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

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git

commit 9e3ce5d98a22e72b6ca2a887c65d485b3cadc74a
Author: yangyang zhong <[email protected]>
AuthorDate: Thu May 29 21:03:49 2025 +0800

    [#6788] feat(authz): Introduce MetadataFilterHelper to filter the result 
data (#7167)
    
    ### What changes were proposed in this pull request?
    
    Introduce MetadataFilterHelper to filter the result data obtained from
    list requests.
    
    ### Why are the changes needed?
    
    Fix: #6788
    
    ### Does this PR introduce _any_ user-facing change?
    
    None
    
    ### How was this patch tested?
    
    org.apache.gravitino.server.authorization.TestMetadataFilterHelper
    
    ---------
    
    Co-authored-by: KyleLin0927 <[email protected]>
---
 .../server/authorization/MetadataFilterHelper.java | 88 ++++++++++++++++---
 .../AuthorizationExpressionEvaluator.java          | 25 ++----
 .../authorization/MockGravitinoAuthorizer.java     | 71 ++++++++++++++++
 .../authorization/TestMetadataFilterHelper.java    | 99 ++++++++++++++++++++++
 .../TestAuthorizationExpressionEvaluator.java      | 53 +-----------
 5 files changed, 254 insertions(+), 82 deletions(-)

diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
index b88ae3d727..ddf25f9b5c 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
@@ -17,10 +17,17 @@
 
 package org.apache.gravitino.server.authorization;
 
-import com.google.errorprone.annotations.DoNotCall;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.gravitino.Entity;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.Privilege;
 import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.PrincipalUtils;
 
 /**
  * MetadataFilterHelper performs permission checks on the list data returned 
by the REST API based
@@ -34,28 +41,89 @@ public class MetadataFilterHelper {
   /**
    * Call {@link GravitinoAuthorizer} to filter the metadata list
    *
-   * @param metadataType for example, CATALOG, SCHEMA,TABLE, etc.
+   * @param entityType for example, CATALOG, SCHEMA,TABLE, etc.
    * @param privilege for example, CREATE_CATALOG, CREATE_TABLE, etc.
    * @param metadataList metadata list.
    * @return metadata List that the user has permission to access.
    */
-  @DoNotCall
-  public static NameIdentifier[] filter(
-      MetadataObject.Type metadataType, String privilege, NameIdentifier[] 
metadataList) {
-    throw new UnsupportedOperationException();
+  public static NameIdentifier[] filterByPrivilege(
+      String metalake,
+      Entity.EntityType entityType,
+      String privilege,
+      NameIdentifier[] metadataList) {
+    GravitinoAuthorizer gravitinoAuthorizer =
+        GravitinoAuthorizerProvider.getInstance().getGravitinoAuthorizer();
+    Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
+    return Arrays.stream(metadataList)
+        .filter(
+            metaDataName ->
+                gravitinoAuthorizer.authorize(
+                    currentPrincipal,
+                    metalake,
+                    NameIdentifierUtil.toMetadataObject(metaDataName, 
entityType),
+                    Privilege.Name.valueOf(privilege)))
+        .toArray(NameIdentifier[]::new);
   }
 
   /**
    * Call {@link AuthorizationExpressionEvaluator} to filter the metadata list
    *
+   * @param metalake metalake
    * @param expression authorization expression
-   * @param metadataType for example, CATALOG, SCHEMA,TABLE, etc.
+   * @param entityType for example, CATALOG, SCHEMA,TABLE, etc.
    * @param nameIdentifiers metaData list.
    * @return metadata List that the user has permission to access.
    */
-  @DoNotCall
   public static NameIdentifier[] filterByExpression(
-      String expression, MetadataObject.Type metadataType, NameIdentifier[] 
nameIdentifiers) {
-    throw new UnsupportedOperationException();
+      String metalake,
+      String expression,
+      Entity.EntityType entityType,
+      NameIdentifier[] nameIdentifiers) {
+    AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
+        new AuthorizationExpressionEvaluator(expression);
+    return Arrays.stream(nameIdentifiers)
+        .filter(
+            metaDataName -> {
+              Map<MetadataObject.Type, NameIdentifier> nameIdentifierMap =
+                  spiltMetadataNames(metalake, entityType, metaDataName);
+              return 
authorizationExpressionEvaluator.evaluate(nameIdentifierMap);
+            })
+        .toArray(NameIdentifier[]::new);
+  }
+
+  /**
+   * Extract the parent metadata from NameIdentifier. For example, when given 
a Table
+   * NameIdentifier, it returns a map containing the Table itself along with 
its parent Schema and
+   * Catalog.
+   *
+   * @param metalake metalake
+   * @param entityType metadata type
+   * @param nameIdentifier metadata name
+   * @return A map containing the metadata object and all its parent objects, 
keyed by their types
+   */
+  private static Map<MetadataObject.Type, NameIdentifier> spiltMetadataNames(
+      String metalake, Entity.EntityType entityType, NameIdentifier 
nameIdentifier) {
+    Map<MetadataObject.Type, NameIdentifier> nameIdentifierMap = new 
HashMap<>();
+    nameIdentifierMap.put(MetadataObject.Type.METALAKE, 
NameIdentifierUtil.ofMetalake(metalake));
+    switch (entityType) {
+      case CATALOG:
+        nameIdentifierMap.put(MetadataObject.Type.CATALOG, nameIdentifier);
+        break;
+      case SCHEMA:
+        nameIdentifierMap.put(MetadataObject.Type.SCHEMA, nameIdentifier);
+        nameIdentifierMap.put(
+            MetadataObject.Type.CATALOG, 
NameIdentifierUtil.getCatalogIdentifier(nameIdentifier));
+        break;
+      case TABLE:
+        nameIdentifierMap.put(MetadataObject.Type.TABLE, nameIdentifier);
+        nameIdentifierMap.put(
+            MetadataObject.Type.SCHEMA, 
NameIdentifierUtil.getSchemaIdentifier(nameIdentifier));
+        nameIdentifierMap.put(
+            MetadataObject.Type.CATALOG, 
NameIdentifierUtil.getCatalogIdentifier(nameIdentifier));
+        break;
+      default:
+        break;
+    }
+    return nameIdentifierMap;
   }
 }
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
index 3f4d429935..4106e442fc 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionEvaluator.java
@@ -22,12 +22,12 @@ import java.util.Map;
 import ognl.Ognl;
 import ognl.OgnlContext;
 import ognl.OgnlException;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Entity;
 import org.apache.gravitino.MetadataObject;
-import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.server.authorization.GravitinoAuthorizer;
 import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
+import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
 
 /** Evaluate the runtime result of the AuthorizationExpression. */
@@ -62,7 +62,9 @@ public class AuthorizationExpressionEvaluator {
     ognlContext.put("authorizer", gravitinoAuthorizer);
     metadataNames.forEach(
         (metadataType, metadataName) -> {
-          MetadataObject metadataObject = buildMetadataObject(metadataType, 
metadataName);
+          MetadataObject metadataObject =
+              NameIdentifierUtil.toMetadataObject(
+                  metadataName, 
Entity.EntityType.valueOf(metadataType.name()));
           ognlContext.put(metadataType.name(), metadataObject);
         });
     NameIdentifier nameIdentifier = 
metadataNames.get(MetadataObject.Type.METALAKE);
@@ -74,21 +76,4 @@ public class AuthorizationExpressionEvaluator {
       throw new RuntimeException("ognl evaluate error", e);
     }
   }
-
-  /**
-   * Build the MetadataObject through metadataType and metadataName.
-   *
-   * @param metadataType metadata type
-   * @param metadataName metadata NameIdentifier
-   * @return MetadataObject
-   */
-  private MetadataObject buildMetadataObject(
-      MetadataObject.Type metadataType, NameIdentifier metadataName) {
-    String namespaceWithMetalake = metadataName.namespace().toString();
-    String metadataParent = StringUtils.substringAfter(namespaceWithMetalake, 
".");
-    if ("".equals(metadataParent)) {
-      return MetadataObjects.of(null, metadataName.name(), metadataType);
-    }
-    return MetadataObjects.of(metadataParent, metadataName.name(), 
metadataType);
-  }
 }
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
new file mode 100644
index 0000000000..38a6c16d17
--- /dev/null
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.server.authorization;
+
+import java.security.Principal;
+import java.util.Objects;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.authorization.Privilege;
+
+/** Mock GravitinoAuthorizer */
+public class MockGravitinoAuthorizer implements GravitinoAuthorizer {
+
+  @Override
+  public void initialize() {}
+
+  @Override
+  public boolean authorize(
+      Principal principal,
+      String metalake,
+      MetadataObject metadataObject,
+      Privilege.Name privilege) {
+    if (!("tester".equals(principal.getName()) && 
"testMetalake".equals(metalake))) {
+      return false;
+    }
+    String name = metadataObject.name();
+    MetadataObject.Type type = metadataObject.type();
+    if (type == MetadataObject.Type.CATALOG
+        && "testCatalog".equals(name)
+        && privilege == Privilege.Name.USE_CATALOG) {
+      return true;
+    }
+    if (type == MetadataObject.Type.SCHEMA
+        && "testSchema".equals(name)
+        && privilege == Privilege.Name.USE_SCHEMA) {
+      return true;
+    }
+    return type == MetadataObject.Type.TABLE
+        && "testTable".equals(name)
+        && privilege == Privilege.Name.SELECT_TABLE;
+  }
+
+  @Override
+  public boolean isOwner(Principal principal, String metalake, MetadataObject 
metadataObject) {
+    if (!("tester".equals(principal.getName()) && 
"metalakeWithOwner".equals(metalake))) {
+      return false;
+    }
+    return Objects.equals(metadataObject.type(), MetadataObject.Type.METALAKE)
+        && Objects.equals("metalakeWithOwner", metadataObject.name());
+  }
+
+  @Override
+  public void handleRolePrivilegeChange(Long roleId) {}
+
+  @Override
+  public void close() {}
+}
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
new file mode 100644
index 0000000000..6bb38f2de7
--- /dev/null
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.server.authorization;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/** Test of {@link MetadataFilterHelper} */
+public class TestMetadataFilterHelper {
+
+  @Test
+  public void testFilter() {
+    try (MockedStatic<PrincipalUtils> principalUtilsMocked = 
mockStatic(PrincipalUtils.class);
+        MockedStatic<GravitinoAuthorizerProvider> mockStatic =
+            mockStatic(GravitinoAuthorizerProvider.class)) {
+      principalUtilsMocked
+          .when(PrincipalUtils::getCurrentPrincipal)
+          .thenReturn(new UserPrincipal("tester"));
+      GravitinoAuthorizerProvider mockedProvider = 
mock(GravitinoAuthorizerProvider.class);
+      
mockStatic.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+      when(mockedProvider.getGravitinoAuthorizer()).thenReturn(new 
MockGravitinoAuthorizer());
+      NameIdentifier[] nameIdentifiers = new NameIdentifier[3];
+      nameIdentifiers[0] = NameIdentifierUtil.ofSchema("testMetalake", 
"testCatalog", "testSchema");
+      nameIdentifiers[1] =
+          NameIdentifierUtil.ofSchema("testMetalake", "testCatalog", 
"testSchema2");
+      nameIdentifiers[2] =
+          NameIdentifierUtil.ofSchema("testMetalake", "testCatalog2", 
"testSchema");
+      NameIdentifier[] filtered =
+          MetadataFilterHelper.filterByPrivilege(
+              "testMetalake",
+              Entity.EntityType.SCHEMA,
+              Privilege.Name.USE_SCHEMA.name(),
+              nameIdentifiers);
+      Assertions.assertEquals(2, filtered.length);
+      Assertions.assertEquals("testMetalake.testCatalog.testSchema", 
filtered[0].toString());
+      Assertions.assertEquals("testMetalake.testCatalog2.testSchema", 
filtered[1].toString());
+    }
+  }
+
+  @Test
+  public void testFilterByExpression() {
+    try (MockedStatic<PrincipalUtils> principalUtilsMocked = 
mockStatic(PrincipalUtils.class);
+        MockedStatic<GravitinoAuthorizerProvider> mockStatic =
+            mockStatic(GravitinoAuthorizerProvider.class)) {
+      principalUtilsMocked
+          .when(PrincipalUtils::getCurrentPrincipal)
+          .thenReturn(new UserPrincipal("tester"));
+      GravitinoAuthorizerProvider mockedProvider = 
mock(GravitinoAuthorizerProvider.class);
+      
mockStatic.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+      when(mockedProvider.getGravitinoAuthorizer()).thenReturn(new 
MockGravitinoAuthorizer());
+      NameIdentifier[] nameIdentifiers = new NameIdentifier[3];
+      nameIdentifiers[0] = NameIdentifierUtil.ofSchema("testMetalake", 
"testCatalog", "testSchema");
+      nameIdentifiers[1] =
+          NameIdentifierUtil.ofSchema("testMetalake", "testCatalog", 
"testSchema2");
+      nameIdentifiers[2] =
+          NameIdentifierUtil.ofSchema("testMetalake", "testCatalog2", 
"testSchema");
+      NameIdentifier[] filtered =
+          MetadataFilterHelper.filterByExpression(
+              "testMetalake",
+              "CATALOG::USE_CATALOG && SCHEMA::USE_SCHEMA",
+              Entity.EntityType.SCHEMA,
+              nameIdentifiers);
+      Assertions.assertEquals(1, filtered.length);
+      Assertions.assertEquals("testMetalake.testCatalog.testSchema", 
filtered[0].toString());
+      NameIdentifier[] filtered2 =
+          MetadataFilterHelper.filterByExpression(
+              "testMetalake", "CATALOG::USE_CATALOG", 
Entity.EntityType.SCHEMA, nameIdentifiers);
+      Assertions.assertEquals(2, filtered2.length);
+      Assertions.assertEquals("testMetalake.testCatalog.testSchema", 
filtered2[0].toString());
+      Assertions.assertEquals("testMetalake.testCatalog.testSchema2", 
filtered2[1].toString());
+    }
+  }
+}
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
index 4b25da563d..d873ee17e6 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
@@ -21,17 +21,13 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.when;
 
-import java.io.IOException;
-import java.security.Principal;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.UserPrincipal;
-import org.apache.gravitino.authorization.Privilege;
-import org.apache.gravitino.server.authorization.GravitinoAuthorizer;
 import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
+import org.apache.gravitino.server.authorization.MockGravitinoAuthorizer;
 import org.apache.gravitino.utils.NameIdentifierUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.junit.jupiter.api.Assertions;
@@ -102,51 +98,4 @@ public class TestAuthorizationExpressionEvaluator {
       
Assertions.assertTrue(authorizationExpressionEvaluator.evaluate(metadataNames));
     }
   }
-
-  private static class MockGravitinoAuthorizer implements GravitinoAuthorizer {
-
-    @Override
-    public void initialize() {}
-
-    @Override
-    public boolean authorize(
-        Principal principal,
-        String metalake,
-        MetadataObject metadataObject,
-        Privilege.Name privilege) {
-      if (!("tester".equals(principal.getName()) && 
"testMetalake".equals(metalake))) {
-        return false;
-      }
-      String name = metadataObject.name();
-      MetadataObject.Type type = metadataObject.type();
-      if (type == MetadataObject.Type.CATALOG
-          && "testCatalog".equals(name)
-          && privilege == Privilege.Name.USE_CATALOG) {
-        return true;
-      }
-      if (type == MetadataObject.Type.SCHEMA
-          && "testSchema".equals(name)
-          && privilege == Privilege.Name.USE_SCHEMA) {
-        return true;
-      }
-      return type == MetadataObject.Type.TABLE
-          && "testTable".equals(name)
-          && privilege == Privilege.Name.SELECT_TABLE;
-    }
-
-    @Override
-    public boolean isOwner(Principal principal, String metalake, 
MetadataObject metadataObject) {
-      if (!("tester".equals(principal.getName()) && 
"metalakeWithOwner".equals(metalake))) {
-        return false;
-      }
-      return Objects.equals(metadataObject.type(), 
MetadataObject.Type.METALAKE)
-          && Objects.equals("metalakeWithOwner", metadataObject.name());
-    }
-
-    @Override
-    public void handleRolePrivilegeChange(Long roleId) {}
-
-    @Override
-    public void close() throws IOException {}
-  }
 }

Reply via email to