This is an automated email from the ASF dual-hosted git repository.
liuxun pushed a commit to branch branch-metadata-authz
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-metadata-authz by this
push:
new ea1b8f430c [#7449]feat(authz): Support Catalog Authorization (#7450)
ea1b8f430c is described below
commit ea1b8f430c8ea8bf8de4498b1206cc7a3a558d64
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 +-
.../apache/gravitino/server/GravitinoServer.java | 7 +-
.../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 +-
13 files changed, 363 insertions(+), 60 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 95af2e0f0f..545b8791d5 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/GravitinoServer.java
b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
index 2a0af11085..c9a45a8124 100644
--- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
+++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java
@@ -41,7 +41,6 @@ import org.apache.gravitino.metalake.MetalakeDispatcher;
import org.apache.gravitino.metrics.MetricsSystem;
import org.apache.gravitino.metrics.source.MetricsSource;
import org.apache.gravitino.server.authentication.ServerAuthenticator;
-import org.apache.gravitino.server.authorization.GravitinoAuthorizer;
import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
import org.apache.gravitino.server.web.ConfigServlet;
import org.apache.gravitino.server.web.HttpServerMetricsSource;
@@ -184,11 +183,7 @@ public class GravitinoServer extends ResourceConfig {
}
public void stop() throws IOException {
- GravitinoAuthorizer gravitinoAuthorizer =
- GravitinoAuthorizerProvider.getInstance().getGravitinoAuthorizer();
- if (gravitinoAuthorizer != null) {
- gravitinoAuthorizer.close();
- }
+ GravitinoAuthorizerProvider.getInstance().close();
server.stop();
gravitinoEnv.shutdown();
if (lineageService != null) {
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