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 6668c1e80c408c0e50aec6f2808058c9276dc7b4 Author: Lord of Abyss <[email protected]> AuthorDate: Thu Apr 24 14:48:50 2025 +0800 [#6775] feat(server): Introduce authorization annotations (#6828) ### What changes were proposed in this pull request? Introduce authorization annotations ### Why are the changes needed? Fix: #6775 ### Does this PR introduce _any_ user-facing change? no ### How was this patch tested? local test. --- .../annotations/AuthorizationExpression.java | 39 +++++++ .../annotations/AuthorizationMetadata.java | 37 +++++++ .../AuthorizationMetadataPrivileges.java | 49 +++++++++ .../authorization/annotations/TestAnnotations.java | 121 +++++++++++++++++++++ 4 files changed, 246 insertions(+) diff --git a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java new file mode 100644 index 0000000000..6173f61158 --- /dev/null +++ b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationExpression.java @@ -0,0 +1,39 @@ +/* + * 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.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to implement unified authentication in AOP. Use Expressions to define the + * required privileges for an API. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizationExpression { + /** + * The expression to evaluate for authorization, which represents multiple privileges. + * + * @return the expression to evaluate for authorization. + */ + String expression() default ""; +} diff --git a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationMetadata.java b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationMetadata.java new file mode 100644 index 0000000000..41d7a3f52b --- /dev/null +++ b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationMetadata.java @@ -0,0 +1,37 @@ +/* + * 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.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.gravitino.MetadataObject; + +/** This annotation identify which parameters in the request are to be used for authorization. */ +@Target({ElementType.PARAMETER, ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizationMetadata { + /** + * The type of the parameter to be used for authorization. + * + * @return the type of the parameter to be used for authorization. + */ + MetadataObject.Type type(); +} diff --git a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationMetadataPrivileges.java b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationMetadataPrivileges.java new file mode 100644 index 0000000000..1e2703ccb7 --- /dev/null +++ b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationMetadataPrivileges.java @@ -0,0 +1,49 @@ +/* + * 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.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.authorization.Privilege; + +/** + * Defines the annotation for authorizing access to an API. Use the resourceType and privileges + * fields to define the required privileges and resource type for the API. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizationMetadataPrivileges { + /** + * The list of privileges required to access the API. + * + * @return the list of privileges required to access the API. + */ + Privilege.Name[] privileges(); + + /** + * The resource type of the API. + * + * @return the resource type of the API. + */ + MetadataObject.Type metadataType(); +} diff --git a/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java b/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java new file mode 100644 index 0000000000..9bad0c47d6 --- /dev/null +++ b/server-common/src/test/java/org/apache/gravitino/server/authorization/annotations/TestAnnotations.java @@ -0,0 +1,121 @@ +/* + * 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.annotations; + +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.authorization.Privilege; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestAnnotations { + + // This class is used to test the AuthorizeResource annotation. + static class TestResourceAnnotationClass { + + public void methodWithAnnotatedParam( + @AuthorizationMetadata(type = MetadataObject.Type.TABLE) String table) { + // dummy method + } + + public void listSchemas( + @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) String metalake, + @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) String catalog) { + // dummy method + } + } + + // This class is used to test the AuthorizeApi annotation. + // 1. ResourceAuthorizeApi + // 2. ExpressionsAuthorizeApi + static class TestAuthorizeAnnotationClass { + @AuthorizationMetadataPrivileges( + privileges = {Privilege.Name.CREATE_CATALOG, Privilege.Name.USE_CATALOG}, + metadataType = MetadataObject.Type.CATALOG) + public void testAuthedMethodUseResourceType() {} + + @AuthorizationExpression(expression = "CATALOG::CREATE_TABLE || TABLE::CREATE_TABLE") + public void testAuthedMethodUseExpression() {} + } + + @Test + void testAuthorizeApiWithResourceType() throws NoSuchMethodException { + Class<TestAuthorizeAnnotationClass> testClass = TestAuthorizeAnnotationClass.class; + Method method = testClass.getMethod("testAuthedMethodUseResourceType"); + + boolean hasAnnotation = method.isAnnotationPresent(AuthorizationMetadataPrivileges.class); + Assertions.assertTrue(hasAnnotation); + + AuthorizationMetadataPrivileges annotation = + method.getAnnotation(AuthorizationMetadataPrivileges.class); + Assertions.assertNotNull(annotation); + + Assertions.assertArrayEquals( + new Privilege.Name[] {Privilege.Name.CREATE_CATALOG, Privilege.Name.USE_CATALOG}, + annotation.privileges()); + Assertions.assertEquals(MetadataObject.Type.CATALOG, annotation.metadataType()); + } + + @Test + void testAuthorizeApiWithExpression() throws NoSuchMethodException { + Class<TestAuthorizeAnnotationClass> testClass = TestAuthorizeAnnotationClass.class; + Method method = testClass.getMethod("testAuthedMethodUseExpression"); + + boolean hasAnnotation = method.isAnnotationPresent(AuthorizationExpression.class); + Assertions.assertTrue(hasAnnotation); + + AuthorizationExpression annotation = method.getAnnotation(AuthorizationExpression.class); + Assertions.assertNotNull(annotation); + + Assertions.assertEquals( + "CATALOG::CREATE_TABLE || TABLE::CREATE_TABLE", annotation.expression()); + } + + @Test + void testParameterAnnotationPresent() throws NoSuchMethodException { + Parameter argument = + TestResourceAnnotationClass.class.getMethod("methodWithAnnotatedParam", String.class) + .getParameters()[0]; + AuthorizationMetadata annotation = argument.getAnnotation(AuthorizationMetadata.class); + Assertions.assertNotNull(annotation); + Assertions.assertEquals(MetadataObject.Type.TABLE, annotation.type()); + } + + @Test + void testAnnotateListSchemas() throws NoSuchMethodException { + Parameter[] arguments = + TestResourceAnnotationClass.class + .getMethod("listSchemas", String.class, String.class) + .getParameters(); + + Parameter argumentMetalake = arguments[0]; + AuthorizationMetadata metalakeAnnotation = + argumentMetalake.getAnnotation(AuthorizationMetadata.class); + Assertions.assertNotNull(metalakeAnnotation); + Assertions.assertEquals(MetadataObject.Type.METALAKE, metalakeAnnotation.type()); + + Parameter argumentCatalog = arguments[1]; + AuthorizationMetadata catalogAnnotation = + argumentCatalog.getAnnotation(AuthorizationMetadata.class); + Assertions.assertNotNull(catalogAnnotation); + Assertions.assertEquals(MetadataObject.Type.CATALOG, catalogAnnotation.type()); + } +}
