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

mchades pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new 35031e7bf2 [#13839] fix(server): Enforce metadata tag permissions on 
branch-1.3 (#13843)
35031e7bf2 is described below

commit 35031e7bf20657c0c35ecdb6b4431f7f5192ab72
Author: mchades <[email protected]>
AuthorDate: Thu Oct 15 09:34:28 2026 +0800

    [#13839] fix(server): Enforce metadata tag permissions on branch-1.3 
(#13843)
    
    ### What changes were proposed in this pull request?
    
    Apply the server fix from #13794 to `branch-1.3`: group the complete
    `CAN_ACCESS_METADATA` expression and add regression coverage for both
    tag REST routes and Java client tag reads, using 1.3's supported types
    and privileges.
    
    ### Why are the changes needed?
    
    Metadata access could bypass tag permissions and expose associated tags
    to unauthorized users.
    
    Fix: #13839 (branch-1.3)
    
    ### Does this PR introduce _any_ user-facing change?
    
    Tag reads through metadata objects now enforce tag permissions.
    
    ### How was this patch tested?
    
    - Reproduced both the expression and Java HTTP regressions before the
    fix; both pass after it.
    - 324 `server-common` tests, 43 server authorization tests, and 10
    `TagOperationsAuthorizationIT` tests passed.
    - Spotless and `git diff --check` passed.
    
    Co-authored-by: Akshay Thorat <[email protected]>
---
 .../TagOperationsAuthorizationIT.java              |  7 ++-
 .../AuthorizationExpressionConverter.java          |  4 +-
 ...stMetadataObjectTagAuthorizationExpression.java | 72 ++++++++++++++++++++++
 3 files changed, 80 insertions(+), 3 deletions(-)

diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
index 01780d8bee..f1f9111669 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
@@ -258,9 +258,10 @@ public class TagOperationsAuthorizationIT extends 
BaseRestApiAuthorizationIT {
         .associateTags(new String[] {"tag3"}, null);
   }
 
+  /** Verify metadata access does not bypass tag permissions when listing or 
reading tags. */
   @Test
   @Order(7)
-  public void testListTagByMetadata() {
+  public void testListAndGetTagByMetadata() {
     GravitinoMetalake gravitinoMetalake = client.loadMetalake(METALAKE);
     SupportsTags tableSupportTag =
         gravitinoMetalake
@@ -281,6 +282,10 @@ public class TagOperationsAuthorizationIT extends 
BaseRestApiAuthorizationIT {
     String[] tagsLoadByNormalUser = tableSupportTagByNormalUser.listTags();
     Arrays.sort(tagsLoadByNormalUser);
     assertArrayEquals(new String[] {"tag2", "tag3"}, tagsLoadByNormalUser);
+    assertEquals("tag1", tableSupportTag.getTag("tag1").name());
+    assertEquals("tag2", tableSupportTagByNormalUser.getTag("tag2").name());
+    assertEquals("tag3", tableSupportTagByNormalUser.getTag("tag3").name());
+    assertThrows(ForbiddenException.class, () -> 
tableSupportTagByNormalUser.getTag("tag1"));
   }
 
   @Test
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 f98ea1ea02..3edc2c3155 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
@@ -171,7 +171,7 @@ public class AuthorizationExpressionConverter {
     return expression.replaceAll(
         AuthorizationExpressionConstants.CAN_ACCESS_METADATA,
         """
-              ( entityType == 'CATALOG' && (%s)) ||
+              (( entityType == 'CATALOG' && (%s)) ||
               ( entityType == 'SCHEMA' && (%s)) ||
               ( entityType == 'TABLE' && (%s)) ||
               ( entityType == 'VIEW' && (%s)) ||
@@ -185,7 +185,7 @@ public class AuthorizationExpressionConverter {
               ( entityType == 'JOB' && (%s)) ||
               ( entityType == 'JOB_TEMPLATE' && (%s)) ||
               ( entityType == 'COLUMN' && (%s)) ||
-              ( entityType == 'FUNCTION' && (%s))
+              ( entityType == 'FUNCTION' && (%s)))
               """
             .formatted(
                 LOAD_CATALOG_AUTHORIZATION_EXPRESSION,
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestMetadataObjectTagAuthorizationExpression.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestMetadataObjectTagAuthorizationExpression.java
new file mode 100644
index 0000000000..6d0816daae
--- /dev/null
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/authorization/TestMetadataObjectTagAuthorizationExpression.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.server.web.rest.authorization;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.common.collect.ImmutableSet;
+import java.lang.reflect.Method;
+import 
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
+import 
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionConverter;
+import org.apache.gravitino.server.web.rest.MetadataObjectTagOperations;
+import org.apache.gravitino.server.web.rest.TagOperations;
+import org.junit.jupiter.api.Test;
+
+/** Tests that metadata access cannot bypass tag read authorization. */
+public class TestMetadataObjectTagAuthorizationExpression {
+
+  /** Verify tag reads require both metadata access and tag permission on both 
REST routes. */
+  @Test
+  public void testGetTagRequiresTagPermission() throws Exception {
+    for (Class<?> resource :
+        new Class<?>[] {MetadataObjectTagOperations.class, 
TagOperations.class}) {
+      Method method =
+          resource.getMethod(
+              "getTagForObject", String.class, String.class, String.class, 
String.class);
+      String expression = 
method.getAnnotation(AuthorizationExpression.class).expression();
+      for (String type : new String[] {"TABLE", "VIEW"}) {
+        MockAuthorizationExpressionEvaluator evaluator =
+            new MockAuthorizationExpressionEvaluator(
+                
AuthorizationExpressionConverter.replaceCanAccessMetadataPrivilege(expression)
+                    .replace("entityType", "'" + type + "'"));
+        String context = resource.getSimpleName() + ": " + type;
+        String access = type.equals("TABLE") ? "TABLE::SELECT_TABLE" : 
"VIEW::SELECT_VIEW";
+        ImmutableSet<String> metadataAccess =
+            ImmutableSet.of("CATALOG::USE_CATALOG", "SCHEMA::USE_SCHEMA", 
access);
+        assertFalse(evaluator.getResult(metadataAccess), context);
+        for (String tagPrivilege : new String[] {"TAG::APPLY_TAG", 
"TAG::OWNER"}) {
+          assertFalse(evaluator.getResult(ImmutableSet.of(tagPrivilege)), 
context);
+          assertTrue(
+              evaluator.getResult(
+                  
ImmutableSet.<String>builder().addAll(metadataAccess).add(tagPrivilege).build()),
+              context);
+        }
+        assertFalse(
+            evaluator.getResult(
+                ImmutableSet.<String>builder()
+                    .addAll(metadataAccess)
+                    .add("TAG::APPLY_TAG", "TAG::DENY_APPLY_TAG")
+                    .build()),
+            context);
+        assertTrue(evaluator.getResult(ImmutableSet.of("METALAKE::OWNER")), 
context);
+      }
+    }
+  }
+}

Reply via email to