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 42b2ffde69 [#6788] feat(authz): Introduce MetadataFilterHelper to
filter the result data (#7167)
42b2ffde69 is described below
commit 42b2ffde69719268fbcab5f84b45dbb1992f669e
Author: yangyang zhong <[email protected]>
AuthorDate: Thu May 29 21:03:49 2025 +0800
[#6788] feat(authz): Introduce MetadataFilterHelper to filter the result
data (#7167)
### What changes were proposed in this pull request?
Introduce MetadataFilterHelper to filter the result data obtained from
list requests.
### Why are the changes needed?
Fix: #6788
### Does this PR introduce _any_ user-facing change?
None
### How was this patch tested?
org.apache.gravitino.server.authorization.TestMetadataFilterHelper
---------
Co-authored-by: KyleLin0927 <[email protected]>
---
.../server/authorization/MetadataFilterHelper.java | 88 ++++++++++++++++---
.../AuthorizationExpressionEvaluator.java | 25 ++----
.../authorization/MockGravitinoAuthorizer.java | 71 ++++++++++++++++
.../authorization/TestMetadataFilterHelper.java | 99 ++++++++++++++++++++++
.../TestAuthorizationExpressionEvaluator.java | 53 +-----------
5 files changed, 254 insertions(+), 82 deletions(-)
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
index b88ae3d727..ddf25f9b5c 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataFilterHelper.java
@@ -17,10 +17,17 @@
package org.apache.gravitino.server.authorization;
-import com.google.errorprone.annotations.DoNotCall;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.Privilege;
import
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.PrincipalUtils;
/**
* MetadataFilterHelper performs permission checks on the list data returned
by the REST API based
@@ -34,28 +41,89 @@ public class MetadataFilterHelper {
/**
* Call {@link GravitinoAuthorizer} to filter the metadata list
*
- * @param metadataType for example, CATALOG, SCHEMA,TABLE, etc.
+ * @param entityType for example, CATALOG, SCHEMA,TABLE, etc.
* @param privilege for example, CREATE_CATALOG, CREATE_TABLE, etc.
* @param metadataList metadata list.
* @return metadata List that the user has permission to access.
*/
- @DoNotCall
- public static NameIdentifier[] filter(
- MetadataObject.Type metadataType, String privilege, NameIdentifier[]
metadataList) {
- throw new UnsupportedOperationException();
+ public static NameIdentifier[] filterByPrivilege(
+ String metalake,
+ Entity.EntityType entityType,
+ String privilege,
+ NameIdentifier[] metadataList) {
+ GravitinoAuthorizer gravitinoAuthorizer =
+ GravitinoAuthorizerProvider.getInstance().getGravitinoAuthorizer();
+ Principal currentPrincipal = PrincipalUtils.getCurrentPrincipal();
+ return Arrays.stream(metadataList)
+ .filter(
+ metaDataName ->
+ gravitinoAuthorizer.authorize(
+ currentPrincipal,
+ metalake,
+ NameIdentifierUtil.toMetadataObject(metaDataName,
entityType),
+ Privilege.Name.valueOf(privilege)))
+ .toArray(NameIdentifier[]::new);
}
/**
* Call {@link AuthorizationExpressionEvaluator} to filter the metadata list
*
+ * @param metalake metalake
* @param expression authorization expression
- * @param metadataType for example, CATALOG, SCHEMA,TABLE, etc.
+ * @param entityType for example, CATALOG, SCHEMA,TABLE, etc.
* @param nameIdentifiers metaData list.
* @return metadata List that the user has permission to access.
*/
- @DoNotCall
public static NameIdentifier[] filterByExpression(
- String expression, MetadataObject.Type metadataType, NameIdentifier[]
nameIdentifiers) {
- throw new UnsupportedOperationException();
+ String metalake,
+ String expression,
+ Entity.EntityType entityType,
+ NameIdentifier[] nameIdentifiers) {
+ AuthorizationExpressionEvaluator authorizationExpressionEvaluator =
+ new AuthorizationExpressionEvaluator(expression);
+ return Arrays.stream(nameIdentifiers)
+ .filter(
+ metaDataName -> {
+ Map<MetadataObject.Type, NameIdentifier> nameIdentifierMap =
+ spiltMetadataNames(metalake, entityType, metaDataName);
+ return
authorizationExpressionEvaluator.evaluate(nameIdentifierMap);
+ })
+ .toArray(NameIdentifier[]::new);
+ }
+
+ /**
+ * Extract the parent metadata from NameIdentifier. For example, when given
a Table
+ * NameIdentifier, it returns a map containing the Table itself along with
its parent Schema and
+ * Catalog.
+ *
+ * @param metalake metalake
+ * @param entityType metadata type
+ * @param nameIdentifier metadata name
+ * @return A map containing the metadata object and all its parent objects,
keyed by their types
+ */
+ private static Map<MetadataObject.Type, NameIdentifier> spiltMetadataNames(
+ String metalake, Entity.EntityType entityType, NameIdentifier
nameIdentifier) {
+ Map<MetadataObject.Type, NameIdentifier> nameIdentifierMap = new
HashMap<>();
+ nameIdentifierMap.put(MetadataObject.Type.METALAKE,
NameIdentifierUtil.ofMetalake(metalake));
+ switch (entityType) {
+ case CATALOG:
+ nameIdentifierMap.put(MetadataObject.Type.CATALOG, nameIdentifier);
+ break;
+ case SCHEMA:
+ nameIdentifierMap.put(MetadataObject.Type.SCHEMA, nameIdentifier);
+ nameIdentifierMap.put(
+ MetadataObject.Type.CATALOG,
NameIdentifierUtil.getCatalogIdentifier(nameIdentifier));
+ break;
+ case TABLE:
+ nameIdentifierMap.put(MetadataObject.Type.TABLE, nameIdentifier);
+ nameIdentifierMap.put(
+ MetadataObject.Type.SCHEMA,
NameIdentifierUtil.getSchemaIdentifier(nameIdentifier));
+ nameIdentifierMap.put(
+ MetadataObject.Type.CATALOG,
NameIdentifierUtil.getCatalogIdentifier(nameIdentifier));
+ break;
+ default:
+ break;
+ }
+ return nameIdentifierMap;
}
}
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 3f4d429935..4106e442fc 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
@@ -22,12 +22,12 @@ import java.util.Map;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
-import org.apache.gravitino.MetadataObjects;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.server.authorization.GravitinoAuthorizer;
import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
+import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.PrincipalUtils;
/** Evaluate the runtime result of the AuthorizationExpression. */
@@ -62,7 +62,9 @@ public class AuthorizationExpressionEvaluator {
ognlContext.put("authorizer", gravitinoAuthorizer);
metadataNames.forEach(
(metadataType, metadataName) -> {
- MetadataObject metadataObject = buildMetadataObject(metadataType,
metadataName);
+ MetadataObject metadataObject =
+ NameIdentifierUtil.toMetadataObject(
+ metadataName,
Entity.EntityType.valueOf(metadataType.name()));
ognlContext.put(metadataType.name(), metadataObject);
});
NameIdentifier nameIdentifier =
metadataNames.get(MetadataObject.Type.METALAKE);
@@ -74,21 +76,4 @@ public class AuthorizationExpressionEvaluator {
throw new RuntimeException("ognl evaluate error", e);
}
}
-
- /**
- * Build the MetadataObject through metadataType and metadataName.
- *
- * @param metadataType metadata type
- * @param metadataName metadata NameIdentifier
- * @return MetadataObject
- */
- private MetadataObject buildMetadataObject(
- MetadataObject.Type metadataType, NameIdentifier metadataName) {
- String namespaceWithMetalake = metadataName.namespace().toString();
- String metadataParent = StringUtils.substringAfter(namespaceWithMetalake,
".");
- if ("".equals(metadataParent)) {
- return MetadataObjects.of(null, metadataName.name(), metadataType);
- }
- return MetadataObjects.of(metadataParent, metadataName.name(),
metadataType);
- }
}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
new file mode 100644
index 0000000000..38a6c16d17
--- /dev/null
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/MockGravitinoAuthorizer.java
@@ -0,0 +1,71 @@
+/*
+ * 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.authorization;
+
+import java.security.Principal;
+import java.util.Objects;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.authorization.Privilege;
+
+/** Mock GravitinoAuthorizer */
+public class MockGravitinoAuthorizer implements GravitinoAuthorizer {
+
+ @Override
+ public void initialize() {}
+
+ @Override
+ public boolean authorize(
+ Principal principal,
+ String metalake,
+ MetadataObject metadataObject,
+ Privilege.Name privilege) {
+ if (!("tester".equals(principal.getName()) &&
"testMetalake".equals(metalake))) {
+ return false;
+ }
+ String name = metadataObject.name();
+ MetadataObject.Type type = metadataObject.type();
+ if (type == MetadataObject.Type.CATALOG
+ && "testCatalog".equals(name)
+ && privilege == Privilege.Name.USE_CATALOG) {
+ return true;
+ }
+ if (type == MetadataObject.Type.SCHEMA
+ && "testSchema".equals(name)
+ && privilege == Privilege.Name.USE_SCHEMA) {
+ return true;
+ }
+ return type == MetadataObject.Type.TABLE
+ && "testTable".equals(name)
+ && privilege == Privilege.Name.SELECT_TABLE;
+ }
+
+ @Override
+ public boolean isOwner(Principal principal, String metalake, MetadataObject
metadataObject) {
+ if (!("tester".equals(principal.getName()) &&
"metalakeWithOwner".equals(metalake))) {
+ return false;
+ }
+ return Objects.equals(metadataObject.type(), MetadataObject.Type.METALAKE)
+ && Objects.equals("metalakeWithOwner", metadataObject.name());
+ }
+
+ @Override
+ public void handleRolePrivilegeChange(Long roleId) {}
+
+ @Override
+ public void close() {}
+}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
new file mode 100644
index 0000000000..6bb38f2de7
--- /dev/null
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/TestMetadataFilterHelper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.authorization;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.when;
+
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.UserPrincipal;
+import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/** Test of {@link MetadataFilterHelper} */
+public class TestMetadataFilterHelper {
+
+ @Test
+ public void testFilter() {
+ try (MockedStatic<PrincipalUtils> principalUtilsMocked =
mockStatic(PrincipalUtils.class);
+ MockedStatic<GravitinoAuthorizerProvider> mockStatic =
+ mockStatic(GravitinoAuthorizerProvider.class)) {
+ principalUtilsMocked
+ .when(PrincipalUtils::getCurrentPrincipal)
+ .thenReturn(new UserPrincipal("tester"));
+ GravitinoAuthorizerProvider mockedProvider =
mock(GravitinoAuthorizerProvider.class);
+
mockStatic.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+ when(mockedProvider.getGravitinoAuthorizer()).thenReturn(new
MockGravitinoAuthorizer());
+ NameIdentifier[] nameIdentifiers = new NameIdentifier[3];
+ nameIdentifiers[0] = NameIdentifierUtil.ofSchema("testMetalake",
"testCatalog", "testSchema");
+ nameIdentifiers[1] =
+ NameIdentifierUtil.ofSchema("testMetalake", "testCatalog",
"testSchema2");
+ nameIdentifiers[2] =
+ NameIdentifierUtil.ofSchema("testMetalake", "testCatalog2",
"testSchema");
+ NameIdentifier[] filtered =
+ MetadataFilterHelper.filterByPrivilege(
+ "testMetalake",
+ Entity.EntityType.SCHEMA,
+ Privilege.Name.USE_SCHEMA.name(),
+ nameIdentifiers);
+ Assertions.assertEquals(2, filtered.length);
+ Assertions.assertEquals("testMetalake.testCatalog.testSchema",
filtered[0].toString());
+ Assertions.assertEquals("testMetalake.testCatalog2.testSchema",
filtered[1].toString());
+ }
+ }
+
+ @Test
+ public void testFilterByExpression() {
+ try (MockedStatic<PrincipalUtils> principalUtilsMocked =
mockStatic(PrincipalUtils.class);
+ MockedStatic<GravitinoAuthorizerProvider> mockStatic =
+ mockStatic(GravitinoAuthorizerProvider.class)) {
+ principalUtilsMocked
+ .when(PrincipalUtils::getCurrentPrincipal)
+ .thenReturn(new UserPrincipal("tester"));
+ GravitinoAuthorizerProvider mockedProvider =
mock(GravitinoAuthorizerProvider.class);
+
mockStatic.when(GravitinoAuthorizerProvider::getInstance).thenReturn(mockedProvider);
+ when(mockedProvider.getGravitinoAuthorizer()).thenReturn(new
MockGravitinoAuthorizer());
+ NameIdentifier[] nameIdentifiers = new NameIdentifier[3];
+ nameIdentifiers[0] = NameIdentifierUtil.ofSchema("testMetalake",
"testCatalog", "testSchema");
+ nameIdentifiers[1] =
+ NameIdentifierUtil.ofSchema("testMetalake", "testCatalog",
"testSchema2");
+ nameIdentifiers[2] =
+ NameIdentifierUtil.ofSchema("testMetalake", "testCatalog2",
"testSchema");
+ NameIdentifier[] filtered =
+ MetadataFilterHelper.filterByExpression(
+ "testMetalake",
+ "CATALOG::USE_CATALOG && SCHEMA::USE_SCHEMA",
+ Entity.EntityType.SCHEMA,
+ nameIdentifiers);
+ Assertions.assertEquals(1, filtered.length);
+ Assertions.assertEquals("testMetalake.testCatalog.testSchema",
filtered[0].toString());
+ NameIdentifier[] filtered2 =
+ MetadataFilterHelper.filterByExpression(
+ "testMetalake", "CATALOG::USE_CATALOG",
Entity.EntityType.SCHEMA, nameIdentifiers);
+ Assertions.assertEquals(2, filtered2.length);
+ Assertions.assertEquals("testMetalake.testCatalog.testSchema",
filtered2[0].toString());
+ Assertions.assertEquals("testMetalake.testCatalog.testSchema2",
filtered2[1].toString());
+ }
+ }
+}
diff --git
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
index 4b25da563d..d873ee17e6 100644
---
a/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
+++
b/server-common/src/test/java/org/apache/gravitino/server/authorization/expression/TestAuthorizationExpressionEvaluator.java
@@ -21,17 +21,13 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
-import java.io.IOException;
-import java.security.Principal;
import java.util.HashMap;
import java.util.Map;
-import java.util.Objects;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.UserPrincipal;
-import org.apache.gravitino.authorization.Privilege;
-import org.apache.gravitino.server.authorization.GravitinoAuthorizer;
import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider;
+import org.apache.gravitino.server.authorization.MockGravitinoAuthorizer;
import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.PrincipalUtils;
import org.junit.jupiter.api.Assertions;
@@ -102,51 +98,4 @@ public class TestAuthorizationExpressionEvaluator {
Assertions.assertTrue(authorizationExpressionEvaluator.evaluate(metadataNames));
}
}
-
- private static class MockGravitinoAuthorizer implements GravitinoAuthorizer {
-
- @Override
- public void initialize() {}
-
- @Override
- public boolean authorize(
- Principal principal,
- String metalake,
- MetadataObject metadataObject,
- Privilege.Name privilege) {
- if (!("tester".equals(principal.getName()) &&
"testMetalake".equals(metalake))) {
- return false;
- }
- String name = metadataObject.name();
- MetadataObject.Type type = metadataObject.type();
- if (type == MetadataObject.Type.CATALOG
- && "testCatalog".equals(name)
- && privilege == Privilege.Name.USE_CATALOG) {
- return true;
- }
- if (type == MetadataObject.Type.SCHEMA
- && "testSchema".equals(name)
- && privilege == Privilege.Name.USE_SCHEMA) {
- return true;
- }
- return type == MetadataObject.Type.TABLE
- && "testTable".equals(name)
- && privilege == Privilege.Name.SELECT_TABLE;
- }
-
- @Override
- public boolean isOwner(Principal principal, String metalake,
MetadataObject metadataObject) {
- if (!("tester".equals(principal.getName()) &&
"metalakeWithOwner".equals(metalake))) {
- return false;
- }
- return Objects.equals(metadataObject.type(),
MetadataObject.Type.METALAKE)
- && Objects.equals("metalakeWithOwner", metadataObject.name());
- }
-
- @Override
- public void handleRolePrivilegeChange(Long roleId) {}
-
- @Override
- public void close() throws IOException {}
- }
}