This is an automated email from the ASF dual-hosted git repository. jshao pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 9e3ce5d98a22e72b6ca2a887c65d485b3cadc74a 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 {} - } }
