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(

Reply via email to