This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 60a4950259 [#7826] feat(authz): Support get owner operation
authorization (#8941)
60a4950259 is described below
commit 60a4950259acb61bc1e6d1bb222c6f6f5d72c288
Author: yangyang zhong <[email protected]>
AuthorDate: Wed Oct 29 19:13:53 2025 +0800
[#7826] feat(authz): Support get owner operation authorization (#8941)
### What changes were proposed in this pull request?
Support get owner operation authorization
### Why are the changes needed?
Fix: #7826
### Does this PR introduce _any_ user-facing change?
None
### How was this patch tested?
org.apache.gravitino.client.integration.test.authorization.OwnerAuthorizationIT
---
.../test/authorization/OwnerAuthorizationIT.java | 58 ++++++++++++++++++++++
.../AuthorizationExpressionConstants.java | 2 +
.../AuthorizationExpressionConverter.java | 36 +++++++++++++-
.../AuthorizationExpressionEvaluator.java | 19 ++++---
.../web/filter/GravitinoInterceptionService.java | 19 ++++++-
.../gravitino/server/web/rest/OwnerOperations.java | 12 +++--
6 files changed, 135 insertions(+), 11 deletions(-)
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerAuthorizationIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerAuthorizationIT.java
index a3781441fb..ea14253179 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerAuthorizationIT.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerAuthorizationIT.java
@@ -20,6 +20,7 @@ package
org.apache.gravitino.client.integration.test.authorization;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.Collections;
@@ -305,4 +306,61 @@ public class OwnerAuthorizationIT extends
BaseRestApiAuthorizationIT {
// reset
gravitinoMetalake.deleteRole(tempRole);
}
+
+ @Test
+ @Order(4)
+ public void testGetCatalogOwner() {
+ GravitinoMetalake gravitinoMetalake = client.loadMetalake(METALAKE);
+ gravitinoMetalake.getOwner(
+ MetadataObjects.of(ImmutableList.of(CATALOG),
MetadataObject.Type.CATALOG));
+ GravitinoMetalake gravitinoMetalakeLoadByNormalUser =
normalUserClient.loadMetalake(METALAKE);
+ gravitinoMetalake.revokePrivilegesFromRole(
+ role,
+ MetadataObjects.of(ImmutableList.of(CATALOG),
MetadataObject.Type.CATALOG),
+ ImmutableSet.of(Privileges.UseCatalog.allow()));
+ assertThrows(
+ "Current user can not get owner",
+ ForbiddenException.class,
+ () -> {
+ gravitinoMetalakeLoadByNormalUser.getOwner(
+ MetadataObjects.of(ImmutableList.of(CATALOG),
MetadataObject.Type.CATALOG));
+ });
+ gravitinoMetalake.grantPrivilegesToRole(
+ role,
+ MetadataObjects.of(ImmutableList.of(CATALOG),
MetadataObject.Type.CATALOG),
+ ImmutableSet.of(Privileges.UseCatalog.allow()));
+ }
+
+ @Test
+ @Order(5)
+ public void testGetSchemaOwner() {
+ GravitinoMetalake gravitinoMetalake = client.loadMetalake(METALAKE);
+ gravitinoMetalake.getOwner(
+ MetadataObjects.of(ImmutableList.of(CATALOG, SCHEMA),
MetadataObject.Type.SCHEMA));
+ GravitinoMetalake gravitinoMetalakeLoadByNormalUser =
normalUserClient.loadMetalake(METALAKE);
+ assertThrows(
+ "Current user can not get owner",
+ ForbiddenException.class,
+ () -> {
+ gravitinoMetalakeLoadByNormalUser.getOwner(
+ MetadataObjects.of(ImmutableList.of(CATALOG, SCHEMA),
MetadataObject.Type.SCHEMA));
+ });
+ }
+
+ @Test
+ @Order(6)
+ public void testGetTableOwner() {
+ GravitinoMetalake gravitinoMetalake = client.loadMetalake(METALAKE);
+ gravitinoMetalake.getOwner(
+ MetadataObjects.of(ImmutableList.of(CATALOG, SCHEMA, "table1"),
MetadataObject.Type.TABLE));
+ GravitinoMetalake gravitinoMetalakeLoadByNormalUser =
normalUserClient.loadMetalake(METALAKE);
+ assertThrows(
+ "Current user can not get owner",
+ ForbiddenException.class,
+ () -> {
+ gravitinoMetalakeLoadByNormalUser.getOwner(
+ MetadataObjects.of(
+ ImmutableList.of(CATALOG, SCHEMA, "table1"),
MetadataObject.Type.TABLE));
+ });
+ }
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
index 06bfaf9de4..5d5017d7f1 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConstants.java
@@ -80,4 +80,6 @@ public class AuthorizationExpressionConstants {
ANY_READ_FILESET ||
ANY_WRITE_FILESET
""";
+
+ public static final String getOwnerExpression = "CAN_GET_OWNER";
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
index 40fecc482e..191e2c1b02 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
@@ -17,6 +17,13 @@
package org.apache.gravitino.server.authorization.expression;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.loadCatalogAuthorizationExpression;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.loadFilesetAuthorizationExpression;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.loadModelAuthorizationExpression;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.loadSchemaAuthorizationExpression;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.loadTableAuthorizationExpression;
+import static
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConstants.loadTopicsAuthorizationExpression;
+
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
@@ -73,7 +80,8 @@ public class AuthorizationExpressionConverter {
return EXPRESSION_CACHE.computeIfAbsent(
authorizationExpression,
(expression) -> {
- String replacedExpression =
replaceAnyPrivilege(authorizationExpression);
+ String replacedExpression = replaceGetOwnerPrivilege(expression);
+ replacedExpression = replaceAnyPrivilege(replacedExpression);
replacedExpression = replaceAnyExpressions(replacedExpression);
Matcher matcher = PATTERN.matcher(replacedExpression);
StringBuffer result = new StringBuffer();
@@ -148,6 +156,32 @@ public class AuthorizationExpressionConverter {
return result.toString();
}
+ public static String replaceGetOwnerPrivilege(String expression) {
+ return expression.replaceAll(
+ "CAN_GET_OWNER",
+ """
+ ( entityType == 'CATALOG' && (%s)) ||
+ ( entityType == 'SCHEMA' && (%s)) ||
+ ( entityType == 'TABLE' && (%s)) ||
+ ( entityType == 'MODEL' && (%s)) ||
+ ( entityType == 'FILESET' && (%s)) ||
+ ( entityType == 'TOPIC' && (%s)) ||
+ ( entityType != 'CATALOG' &&
+ entityType != 'SCHEMA' &&
+ entityType != 'TABLE' &&
+ entityType != 'MODEL' &&
+ entityType != 'FILESET' &&
+ entityType != 'TOPIC')
+ """
+ .formatted(
+ loadCatalogAuthorizationExpression,
+ loadSchemaAuthorizationExpression,
+ loadTableAuthorizationExpression,
+ loadModelAuthorizationExpression,
+ loadFilesetAuthorizationExpression,
+ loadTopicsAuthorizationExpression));
+ }
+
/**
* Replace any privilege expression to any expression
*
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 02c455e887..f32ec86385 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
@@ -74,7 +74,8 @@ public class AuthorizationExpressionEvaluator {
Map<Entity.EntityType, NameIdentifier> metadataNames,
AuthorizationRequestContext requestContext) {
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
- return evaluate(metadataNames, new HashMap<>(), requestContext,
currentPrincipal);
+ return evaluate(
+ metadataNames, new HashMap<>(), requestContext, currentPrincipal,
Optional.empty());
}
/**
@@ -84,13 +85,15 @@ public class AuthorizationExpressionEvaluator {
* @param metadataNames key-metadata type, value-metadata NameIdentifier
* @param requestContext authorization request context
* @param principal current principal
+ * @param entityType entityType
* @return authorization result
*/
public boolean evaluate(
Map<Entity.EntityType, NameIdentifier> metadataNames,
AuthorizationRequestContext requestContext,
- Principal principal) {
- return evaluate(metadataNames, new HashMap<>(), requestContext, principal);
+ Principal principal,
+ Optional<String> entityType) {
+ return evaluate(metadataNames, new HashMap<>(), requestContext, principal,
entityType);
}
/**
@@ -99,14 +102,16 @@ public class AuthorizationExpressionEvaluator {
*
* @param metadataNames key-metadata type, value-metadata NameIdentifier
* @param pathParams params from request path
+ * @param entityType entityType
* @return authorization result
*/
public boolean evaluate(
Map<Entity.EntityType, NameIdentifier> metadataNames,
Map<String, Object> pathParams,
- AuthorizationRequestContext requestContext) {
+ AuthorizationRequestContext requestContext,
+ Optional<String> entityType) {
Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
- return evaluate(metadataNames, pathParams, requestContext,
currentPrincipal);
+ return evaluate(metadataNames, pathParams, requestContext,
currentPrincipal, entityType);
}
/**
@@ -123,11 +128,13 @@ public class AuthorizationExpressionEvaluator {
Map<Entity.EntityType, NameIdentifier> metadataNames,
Map<String, Object> pathParams,
AuthorizationRequestContext requestContext,
- Principal currentPrincipal) {
+ Principal currentPrincipal,
+ Optional<String> entityType) {
OgnlContext ognlContext = Ognl.createDefaultContext(null);
ognlContext.put("principal", currentPrincipal);
ognlContext.put("authorizer", authorizer);
ognlContext.put("authorizationContext", requestContext);
+ ognlContext.put("entityType", entityType.orElse(null));
ognlContext.putAll(pathParams);
metadataNames.forEach(
(type, entityNameIdent) -> {
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 b261036ec7..dec93f882f 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
@@ -28,6 +28,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
@@ -131,6 +132,7 @@ public class GravitinoInterceptionService implements
InterceptionService {
if (expressionAnnotation != null) {
String expression = expressionAnnotation.expression();
Object[] args = methodInvocation.getArguments();
+ String entityType =
extractMetadataObjectTypeFromParameters(parameters, args);
Map<Entity.EntityType, NameIdentifier> metadataContext =
extractNameIdentifierFromParameters(parameters, args);
Map<String, Object> pathParams =
extractPathParamsFromParameters(parameters, args);
@@ -138,7 +140,10 @@ public class GravitinoInterceptionService implements
InterceptionService {
new AuthorizationExpressionEvaluator(expression);
boolean authorizeResult =
authorizationExpressionEvaluator.evaluate(
- metadataContext, pathParams, new
AuthorizationRequestContext());
+ metadataContext,
+ pathParams,
+ new AuthorizationRequestContext(),
+ Optional.ofNullable(entityType));
if (!authorizeResult) {
MetadataObject.Type type =
expressionAnnotation.accessMetadataType();
NameIdentifier accessMetadataName =
@@ -322,6 +327,18 @@ public class GravitinoInterceptionService implements
InterceptionService {
}
}
+ private static String extractMetadataObjectTypeFromParameters(
+ Parameter[] parameters, Object[] args) {
+ for (int i = 0; i < parameters.length; i++) {
+ Parameter parameter = parameters[i];
+ AuthorizationObjectType objectType =
parameter.getAnnotation(AuthorizationObjectType.class);
+ if (objectType != null) {
+ return String.valueOf(args[i]).toUpperCase();
+ }
+ }
+ return null;
+ }
+
private static class ClassListFilter implements Filter {
private final Set<String> targetClasses;
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
index 264ba8a2b9..15511a4590 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/OwnerOperations.java
@@ -45,7 +45,9 @@ import org.apache.gravitino.dto.util.DTOConverters;
import org.apache.gravitino.metrics.MetricNames;
import org.apache.gravitino.server.authorization.NameBindings;
import
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
+import
org.apache.gravitino.server.authorization.annotations.AuthorizationFullName;
import
org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata;
+import
org.apache.gravitino.server.authorization.annotations.AuthorizationObjectType;
import org.apache.gravitino.server.web.Utils;
import org.apache.gravitino.utils.MetadataObjectUtil;
@@ -69,10 +71,14 @@ public class OwnerOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "get-object-owner." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "get-object-owner", absolute = true)
+ @AuthorizationExpression(
+ expression = "CAN_GET_OWNER",
+ errorMessage = "Current user can not get this objects's owner")
public Response getOwnerForObject(
- @PathParam("metalake") String metalake,
- @PathParam("metadataObjectType") String metadataObjectType,
- @PathParam("fullName") String fullName) {
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @PathParam("metadataObjectType") @AuthorizationObjectType String
metadataObjectType,
+ @PathParam("fullName") @AuthorizationFullName String fullName) {
try {
MetadataObject object =
MetadataObjects.parse(