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 f2aaa9fb9bb5f9ff7c9663058f68e8ff6e1e0fcc
Author: yangyang zhong <[email protected]>
AuthorDate: Tue Jul 1 12:10:12 2025 +0800

    [#7449]feat(authz): Support Catalog Authorization (#7450)
    
    ### What changes were proposed in this pull request?
    
    Support Catalog Authorization
    
    ### Why are the changes needed?
    
    Fix: #7449
    
    ### Does this PR introduce _any_ user-facing change?
    
    None
    
    ### How was this patch tested?
    
    
org.apache.gravitino.client.integration.test.authorization.CatalogAuthorizationIT
---
 .../authorization/BaseRestApiAuthorizationIT.java  |  81 ++++++++++++++
 .../test/authorization/CatalogAuthorizationIT.java | 118 +++++++++++++++++++++
 .../main/java/org/apache/gravitino/Configs.java    |   7 ++
 .../authentication/AuthenticationFilter.java       |  12 ++-
 .../authorization/GravitinoAuthorizerProvider.java |  22 +++-
 .../annotations/AuthorizationExpression.java       |   8 ++
 .../authorization/annotations/TestAnnotations.java |   4 +-
 .../web/filter/GravitinoInterceptionService.java   |  40 ++++---
 .../server/web/rest/CatalogOperations.java         |  75 +++++++++++--
 .../filter/TestGravitinoInterceptionService.java   |   7 +-
 .../server/web/rest/BaseOperationsTest.java        |  39 ++++---
 .../server/web/rest/TestCatalogOperations.java     |   3 +-
 12 files changed, 362 insertions(+), 54 deletions(-)

diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/BaseRestApiAuthorizationIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/BaseRestApiAuthorizationIT.java
new file mode 100644
index 0000000000..bcf2066cae
--- /dev/null
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/BaseRestApiAuthorizationIT.java
@@ -0,0 +1,81 @@
+/*
+ * 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.client.integration.test.authorization;
+
+import java.io.IOException;
+import java.util.HashMap;
+import org.apache.gravitino.Configs;
+import org.apache.gravitino.client.GravitinoAdminClient;
+import org.apache.gravitino.integration.test.util.BaseIT;
+import org.apache.gravitino.server.authorization.jcasbin.JcasbinAuthorizer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
+
+public class BaseRestApiAuthorizationIT extends BaseIT {
+
+  protected static final String METALAKE = "testMetalake";
+
+  protected static final String USER = "tester";
+
+  protected static final String USER_WITH_AUTHORIZATION = "tester2";
+
+  /** Mock a user without permissions. */
+  protected static GravitinoAdminClient clientWithNoAuthorization;
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(BaseRestApiAuthorizationIT.class);
+
+  @BeforeAll
+  @Override
+  public void startIntegrationTest() throws Exception {
+    // Enable authorization
+    customConfigs.putAll(
+        ImmutableMap.of(
+            "SimpleAuthUserName",
+            USER,
+            Configs.ENABLE_AUTHORIZATION.getKey(),
+            "true",
+            Configs.AUTHORIZATION_IMPL.getKey(),
+            JcasbinAuthorizer.class.getCanonicalName()));
+    super.startIntegrationTest();
+    client.createMetalake(METALAKE, "", new HashMap<>());
+    client.loadMetalake(METALAKE).addUser(USER_WITH_AUTHORIZATION);
+    clientWithNoAuthorization =
+        
GravitinoAdminClient.builder(serverUri).withSimpleAuth(USER_WITH_AUTHORIZATION).build();
+  }
+
+  @AfterAll
+  @Override
+  public void stopIntegrationTest() throws IOException, InterruptedException {
+    client.dropMetalake(METALAKE, true);
+
+    if (clientWithNoAuthorization != null) {
+      clientWithNoAuthorization.close();
+      clientWithNoAuthorization = null;
+    }
+
+    try {
+      closer.close();
+    } catch (Exception e) {
+      LOG.error("Exception in closing CloseableGroup", e);
+    }
+    super.stopIntegrationTest();
+  }
+}
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/CatalogAuthorizationIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/CatalogAuthorizationIT.java
new file mode 100644
index 0000000000..105f7cef66
--- /dev/null
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/CatalogAuthorizationIT.java
@@ -0,0 +1,118 @@
+/*
+ * 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.client.integration.test.authorization;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import com.google.common.collect.Maps;
+import java.util.Map;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.integration.test.container.ContainerSuite;
+import org.apache.gravitino.integration.test.container.HiveContainer;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+@Tag("gravitino-docker-test")
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class CatalogAuthorizationIT extends BaseRestApiAuthorizationIT {
+
+  private String catalog1 = "catalog1";
+
+  private String catalog2 = "catalog2";
+
+  private static final ContainerSuite containerSuite = 
ContainerSuite.getInstance();
+
+  private static String hmsUri;
+
+  @BeforeAll
+  public void startIntegrationTest() throws Exception {
+    containerSuite.startHiveContainer();
+    super.startIntegrationTest();
+    hmsUri =
+        String.format(
+            "thrift://%s:%d",
+            containerSuite.getHiveContainer().getContainerIpAddress(),
+            HiveContainer.HIVE_METASTORE_PORT);
+  }
+
+  @Test
+  @Order(1)
+  public void testCreateCatalog() {
+    Map<String, String> properties = Maps.newHashMap();
+    properties.put("metastore.uris", hmsUri);
+    assertThrows(
+        "Can not access metadata {" + catalog1 + "}.",
+        RuntimeException.class,
+        () -> {
+          clientWithNoAuthorization
+              .loadMetalake(METALAKE)
+              .createCatalog(catalog1, Catalog.Type.RELATIONAL, "hive", 
"comment", properties);
+        });
+    client
+        .loadMetalake(METALAKE)
+        .createCatalog(catalog1, Catalog.Type.RELATIONAL, "hive", "comment", 
properties);
+    client
+        .loadMetalake(METALAKE)
+        .createCatalog(catalog2, Catalog.Type.RELATIONAL, "hive", "comment", 
properties);
+    assertThrows(
+        "Can not access metadata {" + catalog1 + "}.",
+        RuntimeException.class,
+        () -> {
+          clientWithNoAuthorization
+              .loadMetalake(METALAKE)
+              .createCatalog(catalog1, Catalog.Type.RELATIONAL, "hive", 
"comment", properties);
+        });
+  }
+
+  @Test
+  @Order(2)
+  public void testListCatalog() {
+    String[] catalogs = 
clientWithNoAuthorization.loadMetalake(METALAKE).listCatalogs();
+    assertEquals(0, catalogs.length);
+    catalogs = client.loadMetalake(METALAKE).listCatalogs();
+    assertEquals(2, catalogs.length);
+    assertArrayEquals(new String[] {catalog1, catalog2}, catalogs);
+  }
+
+  @Test
+  @Order(3)
+  public void testDeleteCatalog() {
+    String[] catalogs = client.loadMetalake(METALAKE).listCatalogs();
+    assertEquals(2, catalogs.length);
+    assertArrayEquals(new String[] {catalog1, catalog2}, catalogs);
+    assertThrows(
+        "Can not access metadata {" + catalog1 + "}.",
+        RuntimeException.class,
+        () -> {
+          
clientWithNoAuthorization.loadMetalake(METALAKE).dropCatalog(catalog1, true);
+        });
+    client.loadMetalake(METALAKE).dropCatalog(catalog1, true);
+    catalogs = client.loadMetalake(METALAKE).listCatalogs();
+    assertEquals(1, catalogs.length);
+    assertArrayEquals(new String[] {catalog2}, catalogs);
+    client.loadMetalake(METALAKE).dropCatalog(catalog2, true);
+    catalogs = client.loadMetalake(METALAKE).listCatalogs();
+    assertEquals(0, catalogs.length);
+  }
+}
diff --git a/core/src/main/java/org/apache/gravitino/Configs.java 
b/core/src/main/java/org/apache/gravitino/Configs.java
index dd06a0496c..24d445ccb7 100644
--- a/core/src/main/java/org/apache/gravitino/Configs.java
+++ b/core/src/main/java/org/apache/gravitino/Configs.java
@@ -285,6 +285,13 @@ public class Configs {
           .booleanConf()
           .createWithDefault(false);
 
+  public static final ConfigEntry<String> AUTHORIZATION_IMPL =
+      new ConfigBuilder("gravitino.authorization.impl")
+          .doc("Metadata authorization implementation")
+          .version(ConfigConstants.VERSION_1_0_0)
+          .stringConf()
+          
.createWithDefault("org.apache.gravitino.server.authorization.PassThroughAuthorizer");
+
   public static final ConfigEntry<List<String>> SERVICE_ADMINS =
       new ConfigBuilder("gravitino.authorization.serviceAdmins")
           .doc("The admins of Gravitino service")
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java
index 8e376738f7..7df4e68d6a 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authentication/AuthenticationFilter.java
@@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.exceptions.UnauthorizedException;
+import org.apache.gravitino.utils.PrincipalUtils;
 
 public class AuthenticationFilter implements Filter {
 
@@ -82,8 +83,12 @@ public class AuthenticationFilter implements Filter {
       if (principal == null) {
         throw new UnauthorizedException("The provided credentials did not 
support");
       }
-
-      chain.doFilter(request, response);
+      PrincipalUtils.doAs(
+          principal,
+          () -> {
+            chain.doFilter(request, response);
+            return null;
+          });
     } catch (UnauthorizedException ue) {
       HttpServletResponse resp = (HttpServletResponse) response;
       if (!ue.getChallenges().isEmpty()) {
@@ -95,6 +100,9 @@ public class AuthenticationFilter implements Filter {
         }
       }
       resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, ue.getMessage());
+    } catch (Exception e) {
+      HttpServletResponse resp = (HttpServletResponse) response;
+      resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
     }
   }
 
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/GravitinoAuthorizerProvider.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/GravitinoAuthorizerProvider.java
index 3d837ba955..e909ae30a5 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/GravitinoAuthorizerProvider.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/GravitinoAuthorizerProvider.java
@@ -17,9 +17,10 @@
 
 package org.apache.gravitino.server.authorization;
 
+import java.io.Closeable;
+import java.io.IOException;
 import org.apache.gravitino.Configs;
 import org.apache.gravitino.server.ServerConfig;
-import org.apache.gravitino.server.authorization.jcasbin.JcasbinAuthorizer;
 
 /**
  * Used to initialize and store {@link GravitinoAuthorizer}. When Gravitino 
Server starts up, it
@@ -27,7 +28,7 @@ import 
org.apache.gravitino.server.authorization.jcasbin.JcasbinAuthorizer;
  * in the GravitinoAuthorizerProvider. The GravitinoAuthorizer instance can 
then be retrieved using
  * the getGravitinoAuthorizer method.
  */
-public class GravitinoAuthorizerProvider {
+public class GravitinoAuthorizerProvider implements Closeable {
 
   private static final GravitinoAuthorizerProvider INSTANCE = new 
GravitinoAuthorizerProvider();
 
@@ -47,7 +48,14 @@ public class GravitinoAuthorizerProvider {
         if (gravitinoAuthorizer == null) {
           boolean enableAuthorization = 
serverConfig.get(Configs.ENABLE_AUTHORIZATION);
           if (enableAuthorization) {
-            gravitinoAuthorizer = new JcasbinAuthorizer();
+            String authorizationImpl = 
serverConfig.get(Configs.AUTHORIZATION_IMPL);
+            try {
+              gravitinoAuthorizer =
+                  (GravitinoAuthorizer)
+                      
Class.forName(authorizationImpl).getDeclaredConstructor().newInstance();
+            } catch (Exception e) {
+              throw new IllegalArgumentException("Can not initialize 
GravitinoAuthorizer", e);
+            }
           } else {
             gravitinoAuthorizer = new PassThroughAuthorizer();
           }
@@ -69,4 +77,12 @@ public class GravitinoAuthorizerProvider {
   public GravitinoAuthorizer getGravitinoAuthorizer() {
     return gravitinoAuthorizer;
   }
+
+  @Override
+  public void close() throws IOException {
+    if (gravitinoAuthorizer != null) {
+      gravitinoAuthorizer.close();
+    }
+    gravitinoAuthorizer = null;
+  }
 }
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
index 6173f61158..58be4fb206 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
+++ 
b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
@@ -22,6 +22,7 @@ import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import org.apache.gravitino.MetadataObject;
 
 /**
  * This annotation is used to implement unified authentication in AOP. Use 
Expressions to define the
@@ -36,4 +37,11 @@ public @interface AuthorizationExpression {
    * @return the expression to evaluate for authorization.
    */
   String expression() default "";
+
+  /**
+   * Used to identify the type of metadata that needs to be accessed
+   *
+   * @return accessMetadataType
+   */
+  MetadataObject.Type accessMetadataType();
 }
diff --git 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java
 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java
index 75a041eabb..556a0ce368 100644
--- 
a/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java
+++ 
b/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java
@@ -52,7 +52,9 @@ public class TestAnnotations {
         metadataType = MetadataObject.Type.CATALOG)
     public void testAuthedMethodUseResourceType() {}
 
-    @AuthorizationExpression(expression = "CATALOG::CREATE_TABLE || 
TABLE::CREATE_TABLE")
+    @AuthorizationExpression(
+        expression = "CATALOG::CREATE_TABLE || TABLE::CREATE_TABLE",
+        accessMetadataType = MetadataObject.Type.METALAKE)
     public void testAuthedMethodUseExpression() {}
   }
 
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
 
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
index 098490d432..412b78f4f6 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
@@ -34,6 +34,7 @@ import org.aopalliance.intercept.MethodInvocation;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.exceptions.ForbiddenException;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata;
 import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
@@ -82,23 +83,32 @@ public class GravitinoInterceptionService implements 
InterceptionService {
      */
     @Override
     public Object invoke(MethodInvocation methodInvocation) throws Throwable {
-      Method method = methodInvocation.getMethod();
-      Parameter[] parameters = method.getParameters();
-      AuthorizationExpression expressionAnnotation =
-          method.getAnnotation(AuthorizationExpression.class);
-      if (expressionAnnotation != null) {
-        String expression = expressionAnnotation.expression();
-        Object[] args = methodInvocation.getArguments();
-        Map<Entity.EntityType, NameIdentifier> metadataContext =
-            extractNameIdentifierFromParameters(parameters, args);
-        AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
-            new AuthorizationExpressionEvaluator(expression);
-        boolean authorizeResult = 
authorizationExpressionEvaluator.evaluate(metadataContext);
-        if (!authorizeResult) {
-          return Utils.internalError("Can not access metadata.");
+      try {
+        Method method = methodInvocation.getMethod();
+        Parameter[] parameters = method.getParameters();
+        AuthorizationExpression expressionAnnotation =
+            method.getAnnotation(AuthorizationExpression.class);
+        if (expressionAnnotation != null) {
+          String expression = expressionAnnotation.expression();
+          Object[] args = methodInvocation.getArguments();
+          Map<Entity.EntityType, NameIdentifier> metadataContext =
+              extractNameIdentifierFromParameters(parameters, args);
+          AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
+              new AuthorizationExpressionEvaluator(expression);
+          boolean authorizeResult = 
authorizationExpressionEvaluator.evaluate(metadataContext);
+          if (!authorizeResult) {
+            MetadataObject.Type type = 
expressionAnnotation.accessMetadataType();
+            NameIdentifier accessMetadataName =
+                metadataContext.get(Entity.EntityType.valueOf(type.name()));
+            return Utils.forbidden(
+                String.format("Can not access metadata {%s}.", 
accessMetadataName.name()),
+                new ForbiddenException("Can not access metadata {%s}.", 
accessMetadataName));
+          }
         }
+        return methodInvocation.proceed();
+      } catch (Exception ex) {
+        return Utils.forbidden("Can not access metadata. Cause by: " + 
ex.getMessage(), ex);
       }
-      return methodInvocation.proceed();
     }
 
     private Map<Entity.EntityType, NameIdentifier> 
extractNameIdentifierFromParameters(
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
index 9acd635f0c..367d3bfa36 100644
--- 
a/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/CatalogOperations.java
@@ -20,6 +20,8 @@ package org.apache.gravitino.server.web.rest;
 
 import com.codahale.metrics.annotation.ResponseMetered;
 import com.codahale.metrics.annotation.Timed;
+import java.util.Arrays;
+import java.util.stream.Collectors;
 import javax.inject.Inject;
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -38,6 +40,7 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.CatalogChange;
+import org.apache.gravitino.Entity;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
@@ -53,6 +56,8 @@ import org.apache.gravitino.dto.responses.DropResponse;
 import org.apache.gravitino.dto.responses.EntityListResponse;
 import org.apache.gravitino.dto.util.DTOConverters;
 import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.server.authorization.MetadataFilterHelper;
+import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
 import 
org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata;
 import org.apache.gravitino.server.web.Utils;
 import org.apache.gravitino.utils.NameIdentifierUtil;
@@ -95,11 +100,36 @@ public class CatalogOperations {
             // Lock the root and the metalake with WRITE lock to ensure the 
consistency of the list.
             if (verbose) {
               Catalog[] catalogs = 
catalogDispatcher.listCatalogsInfo(catalogNS);
+              Arrays.stream(catalogs)
+                  .filter(
+                      catalog -> {
+                        NameIdentifier[] nameIdentifiers =
+                            new NameIdentifier[] {
+                              NameIdentifierUtil.ofCatalog(metalake, 
catalog.name())
+                            };
+                        return MetadataFilterHelper.filterByExpression(
+                                    metalake,
+                                    "METALAKE::USE_CATALOG || 
CATALOG::USE_CATALOG "
+                                        + "|| METALAKE::OWNER || 
CATALOG::OWNER",
+                                    Entity.EntityType.CATALOG,
+                                    nameIdentifiers)
+                                .length
+                            > 0;
+                      })
+                  .collect(Collectors.toList())
+                  .toArray(new Catalog[0]);
               Response response = Utils.ok(new 
CatalogListResponse(DTOConverters.toDTOs(catalogs)));
               LOG.info("List {} catalogs info under metalake: {}", 
catalogs.length, metalake);
               return response;
             } else {
               NameIdentifier[] idents = 
catalogDispatcher.listCatalogs(catalogNS);
+              idents =
+                  MetadataFilterHelper.filterByExpression(
+                      metalake,
+                      "METALAKE::USE_CATALOG || CATALOG::USE_CATALOG "
+                          + "|| METALAKE::OWNER || CATALOG::OWNER",
+                      Entity.EntityType.CATALOG,
+                      idents);
               Response response = Utils.ok(new EntityListResponse(idents));
               LOG.info("List {} catalogs under metalake: {}", idents.length, 
metalake);
               return response;
@@ -114,6 +144,9 @@ public class CatalogOperations {
   @Produces("application/vnd.gravitino.v1+json")
   @Timed(name = "create-catalog." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
   @ResponseMetered(name = "create-catalog", absolute = true)
+  @AuthorizationExpression(
+      expression = "METALAKE::CREATE_CATALOG || METALAKE::OWNER",
+      accessMetadataType = MetadataObject.Type.CATALOG)
   public Response createCatalog(
       @PathParam("metalake") @AuthorizationMetadata(type = 
MetadataObject.Type.METALAKE)
           String metalake,
@@ -149,7 +182,9 @@ public class CatalogOperations {
   @Timed(name = "test-connection." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
   @ResponseMetered(name = "test-connection", absolute = true)
   public Response testConnection(
-      @PathParam("metalake") String metalake, CatalogCreateRequest request) {
+      @PathParam("metalake") @AuthorizationMetadata(type = 
MetadataObject.Type.METALAKE)
+          String metalake,
+      CatalogCreateRequest request) {
     LOG.info("Received test connection request for catalog: {}.{}", metalake, 
request.getName());
     try {
       return Utils.doAs(
@@ -180,9 +215,15 @@ public class CatalogOperations {
   @Produces("application/vnd.gravitino.v1+json")
   @Timed(name = "set-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute = 
true)
   @ResponseMetered(name = "set-catalog", absolute = true)
+  @AuthorizationExpression(
+      expression =
+          "METALAKE::USE_CATALOG || CATALOG::USE_CATALOG || METALAKE::OWNER || 
CATALOG::OWNER",
+      accessMetadataType = MetadataObject.Type.CATALOG)
   public Response setCatalog(
-      @PathParam("metalake") String metalake,
-      @PathParam("catalog") String catalogName,
+      @PathParam("metalake") @AuthorizationMetadata(type = 
MetadataObject.Type.METALAKE)
+          String metalake,
+      @PathParam("catalog") @AuthorizationMetadata(type = 
MetadataObject.Type.CATALOG)
+          String catalogName,
       CatalogSetRequest request) {
     LOG.info("Received set request for catalog: {}.{}", metalake, catalogName);
     try {
@@ -221,8 +262,15 @@ public class CatalogOperations {
   @Produces("application/vnd.gravitino.v1+json")
   @Timed(name = "load-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
   @ResponseMetered(name = "load-catalog", absolute = true)
+  @AuthorizationExpression(
+      expression =
+          "METALAKE::USE_CATALOG || CATALOG::USE_CATALOG || METALAKE::OWNER || 
CATALOG::OWNER",
+      accessMetadataType = MetadataObject.Type.CATALOG)
   public Response loadCatalog(
-      @PathParam("metalake") String metalakeName, @PathParam("catalog") String 
catalogName) {
+      @PathParam("metalake") @AuthorizationMetadata(type = 
MetadataObject.Type.METALAKE)
+          String metalakeName,
+      @PathParam("catalog") @AuthorizationMetadata(type = 
MetadataObject.Type.CATALOG)
+          String catalogName) {
     LOG.info("Received load catalog request for catalog: {}.{}", metalakeName, 
catalogName);
     try {
       NameIdentifier ident = NameIdentifierUtil.ofCatalog(metalakeName, 
catalogName);
@@ -242,9 +290,14 @@ public class CatalogOperations {
   @Produces("application/vnd.gravitino.v1+json")
   @Timed(name = "alter-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
   @ResponseMetered(name = "alter-catalog", absolute = true)
+  @AuthorizationExpression(
+      expression = "METALAKE::OWNER || CATALOG::OWNER",
+      accessMetadataType = MetadataObject.Type.CATALOG)
   public Response alterCatalog(
-      @PathParam("metalake") String metalakeName,
-      @PathParam("catalog") String catalogName,
+      @PathParam("metalake") @AuthorizationMetadata(type = 
MetadataObject.Type.METALAKE)
+          String metalakeName,
+      @PathParam("catalog") @AuthorizationMetadata(type = 
MetadataObject.Type.CATALOG)
+          String catalogName,
       CatalogUpdatesRequest request) {
     LOG.info("Received alter catalog request for catalog: {}.{}", 
metalakeName, catalogName);
     try {
@@ -274,9 +327,14 @@ public class CatalogOperations {
   @Produces("application/vnd.gravitino.v1+json")
   @Timed(name = "drop-catalog." + MetricNames.HTTP_PROCESS_DURATION, absolute 
= true)
   @ResponseMetered(name = "drop-catalog", absolute = true)
+  @AuthorizationExpression(
+      expression = "METALAKE::OWNER || CATALOG::OWNER",
+      accessMetadataType = MetadataObject.Type.CATALOG)
   public Response dropCatalog(
-      @PathParam("metalake") String metalakeName,
-      @PathParam("catalog") String catalogName,
+      @PathParam("metalake") @AuthorizationMetadata(type = 
MetadataObject.Type.METALAKE)
+          String metalakeName,
+      @PathParam("catalog") @AuthorizationMetadata(type = 
MetadataObject.Type.CATALOG)
+          String catalogName,
       @DefaultValue("false") @QueryParam("force") boolean force) {
     LOG.info("Received drop catalog request for catalog: {}.{}", metalakeName, 
catalogName);
     try {
@@ -288,7 +346,6 @@ public class CatalogOperations {
             if (!dropped) {
               LOG.warn("Failed to drop catalog {} under metalake {}", 
catalogName, metalakeName);
             }
-
             Response response = Utils.ok(new DropResponse(dropped));
             LOG.info("Catalog dropped: {}.{}", metalakeName, catalogName);
             return response;
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
 
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
index 591935c8eb..41a8c8a1d4 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/filter/TestGravitinoInterceptionService.java
@@ -75,13 +75,16 @@ public class TestGravitinoInterceptionService {
       when(methodInvocation.getArguments()).thenReturn(new Object[] 
{"testMetalake2"});
       Response response2 = (Response) 
methodInterceptor.invoke(methodInvocation);
       assertEquals(
-          "Can not access metadata.", ((ErrorResponse) 
response2.getEntity()).getMessage());
+          "Can not access metadata {testMetalake2}.",
+          ((ErrorResponse) response2.getEntity()).getMessage());
     }
   }
 
   public static class TestOperations {
 
-    @AuthorizationExpression(expression = "METALAKE::USE_CATALOG || 
METALAKE::OWNER")
+    @AuthorizationExpression(
+        expression = "METALAKE::USE_CATALOG || METALAKE::OWNER",
+        accessMetadataType = MetadataObject.Type.METALAKE)
     public Response testMethod(
         @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) String 
metalake) {
       return Utils.ok("ok");
diff --git 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/BaseOperationsTest.java
similarity index 54%
copy from 
server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
copy to 
server/src/test/java/org/apache/gravitino/server/web/rest/BaseOperationsTest.java
index 6173f61158..97933126d6 100644
--- 
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/BaseOperationsTest.java
@@ -6,9 +6,7 @@
  * 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
@@ -16,24 +14,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.gravitino.server.authorization.annotations;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+package org.apache.gravitino.server.web.rest;
 
-/**
- * This annotation is used to implement unified authentication in AOP. Use 
Expressions to define the
- * required privileges for an API.
- */
-@Target({ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface AuthorizationExpression {
-  /**
-   * The expression to evaluate for authorization, which represents multiple 
privileges.
-   *
-   * @return the expression to evaluate for authorization.
-   */
-  String expression() default "";
+import java.io.IOException;
+import org.apache.gravitino.server.ServerConfig;
+import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+public abstract class BaseOperationsTest extends JerseyTest {
+
+  @BeforeAll
+  public static void start() {
+    GravitinoAuthorizerProvider.getInstance().initialize(new ServerConfig());
+  }
+
+  @AfterAll
+  public static void stop() throws IOException {
+    GravitinoAuthorizerProvider.getInstance().close();
+  }
 }
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
index 9b8c860933..b902d7c68e 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestCatalogOperations.java
@@ -70,14 +70,13 @@ import org.apache.gravitino.rest.RESTUtils;
 import org.glassfish.hk2.utilities.binding.AbstractBinder;
 import org.glassfish.jersey.client.HttpUrlConnectorProvider;
 import org.glassfish.jersey.server.ResourceConfig;
-import org.glassfish.jersey.test.JerseyTest;
 import org.glassfish.jersey.test.TestProperties;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
-public class TestCatalogOperations extends JerseyTest {
+public class TestCatalogOperations extends BaseOperationsTest {
 
   private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
     @Override


Reply via email to