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);
+ }
+ }
+ }
+}