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

Reply via email to