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


The following commit(s) were added to refs/heads/main by this push:
     new 87b58fee3 [#3733] feat(core): Unified authorization framework (#3946)
87b58fee3 is described below

commit 87b58fee3ce630033857e9665ff0d63dd6d778c6
Author: Xun Liu <[email protected]>
AuthorDate: Wed Jul 10 17:20:14 2024 +0800

    [#3733] feat(core): Unified authorization framework (#3946)
    
    ### What changes were proposed in this pull request?
    
    Provide an authorization hook plugin framework, In the next step we can
    develop an authorization plugin, just like Catalogs.
    + [Unified authorization design
    
document](https://docs.google.com/document/d/1RtKfU0uO-N7OjcrB3DOtY1ZsbhVp3GsLSJ26c_YQosQ/edit)
    
    <img width="937" alt="image"
    
src="https://github.com/apache/gravitino/assets/3677382/b9a06b79-057a-494c-a1be-15691f478de1";>
    
    
    ### Why are the changes needed?
    
    Fix: #3733
    
    ### Does this PR introduce _any_ user-facing change?
    
    N/A
    
    ### How was this patch tested?
    
    CI Passed.
---
 .../java/com/datastrato/gravitino/Catalog.java     |   6 +
 .../gravitino/authorization/RoleChange.java        | 155 +++++++++++++++++++++
 .../gravitino/authorization/SecurableObjects.java  |  14 +-
 build.gradle.kts                                   |   3 +-
 .../catalog/hive/TestHiveCatalogOperations.java    |   3 +-
 .../gravitino/authorization/PermissionManager.java |   2 +-
 .../gravitino/connector/BaseCatalog.java           |  63 +++++++++
 .../connector/BaseCatalogPropertiesMetadata.java   |   7 +
 .../authorization/AuthorizationPlugin.java         |  29 ++++
 .../authorization/AuthorizationProvider.java       |  33 +++++
 .../connector/authorization/BaseAuthorization.java |  64 +++++++++
 .../authorization/RoleAuthorizationPlugin.java     |  70 ++++++++++
 .../UserGroupAuthorizationPlugin.java              | 143 +++++++++++++++++++
 .../com/datastrato/gravitino/meta/RoleEntity.java  |   2 +-
 .../java/com/datastrato/gravitino/TestCatalog.java |   9 ++
 .../connector/authorization/TestAuthorization.java |  95 +++++++++++++
 .../mysql/TestMySQLAuthorization.java              |  37 +++++
 .../mysql/TestMySQLAuthorizationPlugin.java        | 105 ++++++++++++++
 .../ranger/TestRangerAuthorization.java            |  37 +++++
 .../ranger/TestRangerAuthorizationPlugin.java      | 105 ++++++++++++++
 ...o.connector.authorization.AuthorizationProvider |  20 +++
 integration-test/build.gradle.kts                  |   2 +-
 .../test/authorization/ranger/RangerIT.java        |  56 +++++++-
 23 files changed, 1042 insertions(+), 18 deletions(-)

diff --git a/api/src/main/java/com/datastrato/gravitino/Catalog.java 
b/api/src/main/java/com/datastrato/gravitino/Catalog.java
index 2f75cab38..d7627cf14 100644
--- a/api/src/main/java/com/datastrato/gravitino/Catalog.java
+++ b/api/src/main/java/com/datastrato/gravitino/Catalog.java
@@ -88,6 +88,12 @@ public interface Catalog extends Auditable {
    */
   String CLOUD_REGION_CODE = "cloud.region-code";
 
+  /**
+   * This variable is used as a key in properties of catalogs to use 
authorization provider in
+   * Gravitino.
+   */
+  String AUTHORIZATION_PROVIDER = "authorization-provider";
+
   /** @return The name of the catalog. */
   String name();
 
diff --git 
a/api/src/main/java/com/datastrato/gravitino/authorization/RoleChange.java 
b/api/src/main/java/com/datastrato/gravitino/authorization/RoleChange.java
new file mode 100644
index 000000000..4271bc7f0
--- /dev/null
+++ b/api/src/main/java/com/datastrato/gravitino/authorization/RoleChange.java
@@ -0,0 +1,155 @@
+/*
+ * 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 com.datastrato.gravitino.authorization;
+
+import com.datastrato.gravitino.annotation.Evolving;
+
+/** The RoleChange interface defines the public API for managing roles in an 
authorization. */
+@Evolving
+public interface RoleChange {
+  /**
+   * Create a RoleChange to add a securable object into a role.
+   *
+   * @param securableObject The securable object.
+   * @return return a RoleChange for the add securable object.
+   */
+  static RoleChange addSecurableObject(SecurableObject securableObject) {
+    return new AddSecurableObject(securableObject);
+  }
+
+  /**
+   * Create a RoleChange to remove a securable object from a role.
+   *
+   * @param securableObject The securable object.
+   * @return return a RoleChange for the add securable object.
+   */
+  static RoleChange removeSecurableObject(SecurableObject securableObject) {
+    return new RemoveSecurableObject(securableObject);
+  }
+
+  /** A AddSecurableObject to add securable object to role. */
+  final class AddSecurableObject implements RoleChange {
+    private final SecurableObject securableObject;
+
+    private AddSecurableObject(SecurableObject securableObject) {
+      this.securableObject = securableObject;
+    }
+
+    /**
+     * Returns the securable object to be added.
+     *
+     * @return return a securable object.
+     */
+    public SecurableObject getSecurableObject() {
+      return this.securableObject;
+    }
+
+    /**
+     * Compares this AddSecurableObject instance with another object for 
equality. The comparison is
+     * based on the add securable object to role.
+     *
+     * @param o The object to compare with this instance.
+     * @return true if the given object represents the same add securable 
object; false otherwise.
+     */
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      AddSecurableObject that = (AddSecurableObject) o;
+      return securableObject.equals(that.securableObject);
+    }
+
+    /**
+     * Generates a hash code for this AddSecurableObject instance. The hash 
code is based on the add
+     * securable object.
+     *
+     * @return A hash code value for this add securable object operation.
+     */
+    @Override
+    public int hashCode() {
+      return securableObject.hashCode();
+    }
+
+    /**
+     * Returns a string representation of the AddSecurableObject instance. 
This string format
+     * includes the class name followed by the add securable object operation.
+     *
+     * @return A string representation of the AddSecurableObject instance.
+     */
+    @Override
+    public String toString() {
+      return "ADDSECURABLEOBJECT " + securableObject;
+    }
+  }
+
+  /** A RemoveSecurableObject to remove securable object from role. */
+  final class RemoveSecurableObject implements RoleChange {
+    private final SecurableObject securableObject;
+
+    private RemoveSecurableObject(SecurableObject securableObject) {
+      this.securableObject = securableObject;
+    }
+
+    /**
+     * Returns the securable object to be added.
+     *
+     * @return return a securable object.
+     */
+    public SecurableObject getSecurableObject() {
+      return this.securableObject;
+    }
+
+    /**
+     * Compares this RemoveSecurableObject instance with another object for 
equality. The comparison
+     * is based on the add securable object to role.
+     *
+     * @param o The object to compare with this instance.
+     * @return true if the given object represents the same add securable 
object; false otherwise.
+     */
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) return true;
+      if (o == null || getClass() != o.getClass()) return false;
+      RemoveSecurableObject that = (RemoveSecurableObject) o;
+      return securableObject.equals(that.securableObject);
+    }
+
+    /**
+     * Generates a hash code for this RemoveSecurableObject instance. The hash 
code is based on the
+     * add securable object.
+     *
+     * @return A hash code value for this add securable object operation.
+     */
+    @Override
+    public int hashCode() {
+      return securableObject.hashCode();
+    }
+
+    /**
+     * Returns a string representation of the RemoveSecurableObject instance. 
This string format
+     * includes the class name followed by the add securable object operation.
+     *
+     * @return A string representation of the RemoveSecurableObject instance.
+     */
+    @Override
+    public String toString() {
+      return "REMOVESECURABLEOBJECT " + securableObject;
+    }
+  }
+}
diff --git 
a/api/src/main/java/com/datastrato/gravitino/authorization/SecurableObjects.java
 
b/api/src/main/java/com/datastrato/gravitino/authorization/SecurableObjects.java
index fa1ded6ea..1037f06a7 100644
--- 
a/api/src/main/java/com/datastrato/gravitino/authorization/SecurableObjects.java
+++ 
b/api/src/main/java/com/datastrato/gravitino/authorization/SecurableObjects.java
@@ -40,7 +40,7 @@ public class SecurableObjects {
    * @return The created metalake {@link SecurableObject}
    */
   public static SecurableObject ofMetalake(String metalake, List<Privilege> 
privileges) {
-    return of(SecurableObject.Type.METALAKE, Lists.newArrayList(metalake), 
privileges);
+    return of(MetadataObject.Type.METALAKE, Lists.newArrayList(metalake), 
privileges);
   }
 
   /**
@@ -51,7 +51,7 @@ public class SecurableObjects {
    * @return The created catalog {@link SecurableObject}
    */
   public static SecurableObject ofCatalog(String catalog, List<Privilege> 
privileges) {
-    return of(SecurableObject.Type.CATALOG, Lists.newArrayList(catalog), 
privileges);
+    return of(MetadataObject.Type.CATALOG, Lists.newArrayList(catalog), 
privileges);
   }
 
   /**
@@ -66,7 +66,7 @@ public class SecurableObjects {
   public static SecurableObject ofSchema(
       SecurableObject catalog, String schema, List<Privilege> privileges) {
     return of(
-        SecurableObject.Type.SCHEMA, Lists.newArrayList(catalog.fullName(), 
schema), privileges);
+        MetadataObject.Type.SCHEMA, Lists.newArrayList(catalog.fullName(), 
schema), privileges);
   }
 
   /**
@@ -81,7 +81,7 @@ public class SecurableObjects {
       SecurableObject schema, String table, List<Privilege> privileges) {
     List<String> names = 
Lists.newArrayList(DOT_SPLITTER.splitToList(schema.fullName()));
     names.add(table);
-    return of(SecurableObject.Type.TABLE, names, privileges);
+    return of(MetadataObject.Type.TABLE, names, privileges);
   }
 
   /**
@@ -96,7 +96,7 @@ public class SecurableObjects {
       SecurableObject schema, String topic, List<Privilege> privileges) {
     List<String> names = 
Lists.newArrayList(DOT_SPLITTER.splitToList(schema.fullName()));
     names.add(topic);
-    return of(SecurableObject.Type.TOPIC, names, privileges);
+    return of(MetadataObject.Type.TOPIC, names, privileges);
   }
 
   /**
@@ -112,7 +112,7 @@ public class SecurableObjects {
       SecurableObject schema, String fileset, List<Privilege> privileges) {
     List<String> names = 
Lists.newArrayList(DOT_SPLITTER.splitToList(schema.fullName()));
     names.add(fileset);
-    return of(SecurableObject.Type.FILESET, names, privileges);
+    return of(MetadataObject.Type.FILESET, names, privileges);
   }
 
   /**
@@ -125,7 +125,7 @@ public class SecurableObjects {
    * @return The created {@link SecurableObject}
    */
   public static SecurableObject ofAllMetalakes(List<Privilege> privileges) {
-    return new SecurableObjectImpl(null, "*", SecurableObject.Type.METALAKE, 
privileges);
+    return new SecurableObjectImpl(null, "*", MetadataObject.Type.METALAKE, 
privileges);
   }
 
   private static class SecurableObjectImpl extends 
MetadataObjects.MetadataObjectImpl
diff --git a/build.gradle.kts b/build.gradle.kts
index e8d2b477e..1508a0b32 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -487,6 +487,7 @@ tasks.rat {
     "DISCLAIMER.txt",
     "ROADMAP.md",
     "clients/client-python/.pytest_cache/*",
+    "clients/client-python/.venv/*",
     "clients/client-python/gravitino.egg-info/*",
     "clients/client-python/gravitino/utils/exceptions.py",
     "clients/client-python/gravitino/utils/http_client.py"
@@ -709,7 +710,7 @@ fun printDockerCheckInfo() {
   if (dockerTest) {
     println("Using Docker container to run all tests. [$testMode test]")
   } else {
-    println("Run test cases without `gravitino-docker-test` tag 
................ [$testMode test]")
+    println("Run test cases without `gravitino-docker-test` tag .............. 
[$testMode test]")
   }
   println("-----------------------------------------------------------------")
 
diff --git 
a/catalogs/catalog-hive/src/test/java/com/datastrato/gravitino/catalog/hive/TestHiveCatalogOperations.java
 
b/catalogs/catalog-hive/src/test/java/com/datastrato/gravitino/catalog/hive/TestHiveCatalogOperations.java
index edf23bab8..4e6e8d3d3 100644
--- 
a/catalogs/catalog-hive/src/test/java/com/datastrato/gravitino/catalog/hive/TestHiveCatalogOperations.java
+++ 
b/catalogs/catalog-hive/src/test/java/com/datastrato/gravitino/catalog/hive/TestHiveCatalogOperations.java
@@ -84,10 +84,11 @@ class TestHiveCatalogOperations {
     Map<String, PropertyEntry<?>> propertyEntryMap =
         HIVE_PROPERTIES_METADATA.catalogPropertiesMetadata().propertyEntries();
 
-    Assertions.assertEquals(14, propertyEntryMap.size());
+    Assertions.assertEquals(15, propertyEntryMap.size());
     Assertions.assertTrue(propertyEntryMap.containsKey(METASTORE_URIS));
     
Assertions.assertTrue(propertyEntryMap.containsKey(Catalog.PROPERTY_PACKAGE));
     
Assertions.assertTrue(propertyEntryMap.containsKey(BaseCatalog.CATALOG_OPERATION_IMPL));
+    
Assertions.assertTrue(propertyEntryMap.containsKey(BaseCatalog.AUTHORIZATION_PROVIDER));
     Assertions.assertTrue(propertyEntryMap.containsKey(CLIENT_POOL_SIZE));
     Assertions.assertTrue(propertyEntryMap.containsKey(IMPERSONATION_ENABLE));
     Assertions.assertTrue(propertyEntryMap.containsKey(LIST_ALL_TABLES));
diff --git 
a/core/src/main/java/com/datastrato/gravitino/authorization/PermissionManager.java
 
b/core/src/main/java/com/datastrato/gravitino/authorization/PermissionManager.java
index f30f41877..3b24e8cde 100644
--- 
a/core/src/main/java/com/datastrato/gravitino/authorization/PermissionManager.java
+++ 
b/core/src/main/java/com/datastrato/gravitino/authorization/PermissionManager.java
@@ -42,7 +42,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * PermissionManager is used for managing the logic the granting and revoking 
roles. Role is used
- * for manging permissions. GrantManager will filter the invalid roles, too.
+ * for manging permissions. PermissionManager will filter the invalid roles, 
too.
  */
 class PermissionManager {
   private static final Logger LOG = 
LoggerFactory.getLogger(PermissionManager.class);
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalog.java 
b/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalog.java
index bdf79827b..a353f84fe 100644
--- a/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalog.java
+++ b/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalog.java
@@ -22,15 +22,23 @@ import com.datastrato.gravitino.Audit;
 import com.datastrato.gravitino.Catalog;
 import com.datastrato.gravitino.CatalogProvider;
 import com.datastrato.gravitino.annotation.Evolving;
+import com.datastrato.gravitino.connector.authorization.AuthorizationPlugin;
+import com.datastrato.gravitino.connector.authorization.AuthorizationProvider;
+import com.datastrato.gravitino.connector.authorization.BaseAuthorization;
 import com.datastrato.gravitino.connector.capability.Capability;
 import com.datastrato.gravitino.meta.CatalogEntity;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Streams;
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.ServiceLoader;
+import java.util.stream.Collectors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,6 +65,9 @@ public abstract class BaseCatalog<T extends BaseCatalog>
   // The object you used is not stable, don't use it unless you know what you 
are doing.
   @VisibleForTesting public static final String CATALOG_OPERATION_IMPL = 
"ops-impl";
 
+  // Underlying access control system plugin for this catalog.
+  private volatile BaseAuthorization<?> authorization;
+
   private CatalogEntity entity;
 
   private Map<String, String> conf;
@@ -169,12 +180,64 @@ public abstract class BaseCatalog<T extends BaseCatalog>
     return ops;
   }
 
+  public AuthorizationPlugin getAuthorizationPlugin() {
+    if (authorization == null) {
+      synchronized (this) {
+        if (authorization == null) {
+          BaseAuthorization<?> baseAuthorization = 
createAuthorizationPluginInstance();
+          if (baseAuthorization == null) {
+            return null;
+          }
+          authorization = baseAuthorization;
+        }
+      }
+    }
+    return authorization.plugin();
+  }
+
+  private BaseAuthorization<?> createAuthorizationPluginInstance() {
+    String authorizationProvider =
+        (String) catalogPropertiesMetadata().getOrDefault(conf, 
AUTHORIZATION_PROVIDER);
+    if (authorizationProvider == null) {
+      LOG.info("Authorization provider is not set!");
+      return null;
+    }
+
+    ServiceLoader<AuthorizationProvider> loader =
+        ServiceLoader.load(
+            AuthorizationProvider.class, 
Thread.currentThread().getContextClassLoader());
+
+    List<Class<? extends AuthorizationProvider>> providers =
+        Streams.stream(loader.iterator())
+            .filter(p -> p.shortName().equalsIgnoreCase(authorizationProvider))
+            .map(AuthorizationProvider::getClass)
+            .collect(Collectors.toList());
+    if (providers.isEmpty()) {
+      throw new IllegalArgumentException(
+          "No authorization provider found for: " + authorizationProvider);
+    } else if (providers.size() > 1) {
+      throw new IllegalArgumentException(
+          "Multiple authorization providers found for: " + 
authorizationProvider);
+    }
+    try {
+      return (BaseAuthorization<?>)
+          
Iterables.getOnlyElement(providers).getDeclaredConstructor().newInstance();
+    } catch (Exception e) {
+      LOG.error("Failed to create authorization instance", e);
+      throw new RuntimeException(e);
+    }
+  }
+
   @Override
   public void close() throws IOException {
     if (ops != null) {
       ops.close();
       ops = null;
     }
+    if (authorization != null) {
+      authorization.close();
+      authorization = null;
+    }
   }
 
   public Capability capability() {
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalogPropertiesMetadata.java
 
b/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalogPropertiesMetadata.java
index 2eada4e92..d2f115a66 100644
--- 
a/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalogPropertiesMetadata.java
+++ 
b/core/src/main/java/com/datastrato/gravitino/connector/BaseCatalogPropertiesMetadata.java
@@ -48,6 +48,13 @@ public abstract class BaseCatalogPropertiesMetadata extends 
BasePropertiesMetada
                   null,
                   false,
                   false),
+              PropertyEntry.stringImmutablePropertyEntry(
+                  Catalog.AUTHORIZATION_PROVIDER,
+                  "The name of the authorization provider for Gravitino",
+                  false,
+                  null,
+                  false,
+                  false),
               PropertyEntry.enumPropertyEntry(
                   CLOUD_NAME,
                   "The cloud that the catalog is running on",
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/authorization/AuthorizationPlugin.java
 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/AuthorizationPlugin.java
new file mode 100644
index 000000000..4e5fe70cb
--- /dev/null
+++ 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/AuthorizationPlugin.java
@@ -0,0 +1,29 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization;
+
+import java.io.Closeable;
+
+/**
+ * Authorization operations plugin interfaces. <br>
+ * Notice: Because each interface function needs to perform multiple steps in 
the underlying
+ * permission system, the implementation method of these function interface 
must be idempotent.
+ */
+public interface AuthorizationPlugin
+    extends UserGroupAuthorizationPlugin, RoleAuthorizationPlugin, Closeable {}
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/authorization/AuthorizationProvider.java
 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/AuthorizationProvider.java
new file mode 100644
index 000000000..efc7a4948
--- /dev/null
+++ 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/AuthorizationProvider.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization;
+
+/**
+ * An Authorization provider is a class that provides a short name for an 
authorization. <br>
+ * This short name is used when creating an authorization.
+ */
+public interface AuthorizationProvider {
+  /**
+   * The string that represents the authorization that this provider uses. <br>
+   * This is overridden by children to provide a nice alias for the 
authorization.
+   *
+   * @return The string that represents the authorization that this provider 
uses.
+   */
+  String shortName();
+}
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/authorization/BaseAuthorization.java
 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/BaseAuthorization.java
new file mode 100644
index 000000000..9855c2f54
--- /dev/null
+++ 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/BaseAuthorization.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * The abstract base class for Authorization implementations.<br>
+ * A typical authorization always contain {@link AuthorizationPlugin} which is 
used to trigger the
+ * specific operations by the authorization. <br>
+ * For example, a Ranger authorization has a RangerAuthorizationPlugin which 
manipulates Ranger to
+ * management Hive and HDFS permission. <br>
+ *
+ * @param <T> The type of the concrete subclass of BaseAuthorization.
+ */
+public abstract class BaseAuthorization<T extends BaseAuthorization>
+    implements AuthorizationProvider, Closeable {
+  private volatile AuthorizationPlugin plugin = null;
+
+  /**
+   * Creates a new instance of AuthorizationPlugin. <br>
+   * The child class should implement this method to provide a specific 
AuthorizationPlugin instance
+   * regarding that authorization. <br>
+   *
+   * @return A new instance of AuthorizationHook.
+   */
+  protected abstract AuthorizationPlugin newPlugin();
+
+  public AuthorizationPlugin plugin() {
+    if (plugin == null) {
+      synchronized (this) {
+        if (plugin == null) {
+          plugin = newPlugin();
+        }
+      }
+    }
+
+    return plugin;
+  }
+
+  @Override
+  public void close() throws IOException {
+    if (plugin != null) {
+      plugin.close();
+    }
+  }
+}
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/authorization/RoleAuthorizationPlugin.java
 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/RoleAuthorizationPlugin.java
new file mode 100644
index 000000000..6095cc796
--- /dev/null
+++ 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/RoleAuthorizationPlugin.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization;
+
+import com.datastrato.gravitino.authorization.Role;
+import com.datastrato.gravitino.authorization.RoleChange;
+
+/** Interface for authorization Role plugin operation of the underlying access 
control system */
+interface RoleAuthorizationPlugin {
+  /**
+   * After creating a role in Gravitino, this method is called to create the 
role in the underlying
+   * system.<br>
+   *
+   * @param role The entity of the Role.
+   * @return True if the create operation success; False if the create 
operation failed.
+   * @throws RuntimeException If creating the Role encounters storage issues.
+   */
+  Boolean onRoleCreated(Role role) throws RuntimeException;
+
+  /**
+   * After acquiring a role from Gravitino, this method is called to acquire 
the role in the
+   * underlying system.<br>
+   * Because role information is already stored in the Gravitino, so we don't 
need to get the Role
+   * from the underlying access control system. <br>
+   * We only need to check if the Role exists in the underlying access control 
system.
+   *
+   * @param role The entity of the Role.
+   * @return IF exist return true, else return false.
+   * @throws RuntimeException If getting the Role encounters underlying access 
control system
+   *     issues.
+   */
+  Boolean onRoleAcquired(Role role) throws RuntimeException;
+
+  /**
+   * After deleting a role from Gravitino, this method is called to delete the 
role in the
+   * underlying system. <br>
+   *
+   * @param role The entity of the Role.
+   * @return True if the Role was successfully deleted, false only when 
there's no such role
+   * @throws RuntimeException If deleting the Role encounters storage issues.
+   */
+  Boolean onRoleDeleted(Role role) throws RuntimeException;
+
+  /**
+   * After updating a role in Gravitino, this method is called to update the 
role in the underlying
+   * system. <br>
+   *
+   * @param role The entity of the Role.
+   * @param changes role changes apply to the role.
+   * @return True if the update operation is successful; False if the update 
operation fails.
+   * @throws RuntimeException If update role encounters storage issues.
+   */
+  Boolean onRoleUpdated(Role role, RoleChange... changes) throws 
RuntimeException;
+}
diff --git 
a/core/src/main/java/com/datastrato/gravitino/connector/authorization/UserGroupAuthorizationPlugin.java
 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/UserGroupAuthorizationPlugin.java
new file mode 100644
index 000000000..e4f1dc81d
--- /dev/null
+++ 
b/core/src/main/java/com/datastrato/gravitino/connector/authorization/UserGroupAuthorizationPlugin.java
@@ -0,0 +1,143 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization;
+
+import com.datastrato.gravitino.authorization.Group;
+import com.datastrato.gravitino.authorization.Role;
+import com.datastrato.gravitino.authorization.User;
+import java.util.List;
+
+/**
+ * Interface for authorization User and Group plugin operation of the 
underlying access control
+ * system.
+ */
+interface UserGroupAuthorizationPlugin {
+  /**
+   * After adding a User to Gravitino, this method is called to add the User 
to the underlying
+   * system. <br>
+   *
+   * @param user The user entity.
+   * @return True if the add User was successfully added, false if the add 
User failed.
+   * @throws RuntimeException If adding the User encounters storage issues.
+   */
+  Boolean onUserAdded(User user) throws RuntimeException;
+
+  /**
+   * After removing a User from Gravitino, this method is called to remove the 
User from the
+   * underlying system. <br>
+   *
+   * @param user The user entity.
+   * @return True if the User was successfully removed, false if the remove 
User failed.
+   * @throws RuntimeException If removing the User encounters storage issues.
+   */
+  Boolean onUserRemoved(User user) throws RuntimeException;
+
+  /**
+   * After acquiring a User from Gravitino, this method is called to acquire 
the User in the
+   * underlying system. <br>
+   * Because User information is already stored in the Gravitino, so we don't 
need to get the User
+   * from the underlying access control system. <br>
+   * We only need to check if the User exists in the underlying access control 
system.
+   *
+   * @param user The user entity.
+   * @return IF exist return true, else return false.
+   * @throws RuntimeException If getting the User encounters underlying access 
control system
+   *     issues.
+   */
+  Boolean onUserAcquired(User user) throws RuntimeException;
+
+  /**
+   * After adding a Group to Gravitino, this method is called to add the Group 
to the underlying
+   * system. <br>
+   *
+   * @param group The group entity.
+   * @return True if the add Group was successfully added, false if the add 
Group failed.
+   * @throws RuntimeException If adding the Group encounters storage issues.
+   */
+  Boolean onGroupAdded(Group group) throws RuntimeException;
+
+  /**
+   * After removing a Group from Gravitino, this method is called to remove 
the Group from the
+   * underlying system. <br>
+   *
+   * @param group The group entity.
+   * @return True if the remove Group was successfully removed, false if the 
remove Group was
+   *     failed.
+   * @throws RuntimeException If removing the Group encounters storage issues.
+   */
+  Boolean onGroupRemoved(Group group) throws RuntimeException;
+
+  /**
+   * After acquiring a Group from Gravitino, this method is called to acquire 
the Group in the
+   * underlying system. <br>
+   * Because Group information is already stored in the Gravitino, so we don't 
need to get the Group
+   * from the underlying access control system. <br>
+   * We only need to check if the Group exists in the underlying access 
control system. <br>
+   *
+   * @param group The group entity.
+   * @return If exist return true, else return false.
+   * @throws RuntimeException If getting the Group encounters underlying 
access control system
+   *     issues.
+   */
+  Boolean onGroupAcquired(Group group) throws RuntimeException;
+
+  /**
+   * After granting roles to a user from Gravitino, this method is called to 
grant roles to the user
+   * in the underlying system. <br>
+   *
+   * @param user The entity of the User.
+   * @param roles The entities of the Roles.
+   * @return True if the Grant was successful, false if the Grant was failed.
+   * @throws RuntimeException If granting roles to a user encounters storage 
issues.
+   */
+  Boolean onGrantedRolesToUser(List<Role> roles, User user) throws 
RuntimeException;
+
+  /**
+   * After revoking roles from a user from Gravitino, this method is called to 
revoke roles from the
+   * user in the underlying system. <br>
+   *
+   * @param user The entity of the User.
+   * @param roles The entities of the Roles.
+   * @return True if the revoke was successfully removed, false if the revoke 
failed.
+   * @throws RuntimeException If revoking roles from a user encounters storage 
issues.
+   */
+  Boolean onRevokedRolesFromUser(List<Role> roles, User user) throws 
RuntimeException;
+
+  /**
+   * After granting roles to a group from Gravitino, this method is called to 
grant roles to the
+   * group in the underlying system. <br>
+   *
+   * @param group The entity of the Group.
+   * @param roles The entities of the Roles.
+   * @return True if the revoke was successfully removed, False if the revoke 
failed.
+   * @throws RuntimeException If granting roles to a group encounters storage 
issues.
+   */
+  Boolean onGrantedRolesToGroup(List<Role> roles, Group group) throws 
RuntimeException;
+
+  /**
+   * After revoking roles from a group from Gravitino, this method is called 
to revoke roles from
+   * the group in the underlying system. <br>
+   *
+   * @param group The entity of the Group.
+   * @param roles The entities of the Roles.
+   * @return True if the revoke was successfully removed, False if the revoke 
failed.
+   * @throws RuntimeException If revoking roles from a group encounters 
storage issues.
+   */
+  Boolean onRevokedRolesFromGroup(List<Role> roles, Group group) throws 
RuntimeException;
+}
diff --git a/core/src/main/java/com/datastrato/gravitino/meta/RoleEntity.java 
b/core/src/main/java/com/datastrato/gravitino/meta/RoleEntity.java
index e1101ce9b..2c2c4a76b 100644
--- a/core/src/main/java/com/datastrato/gravitino/meta/RoleEntity.java
+++ b/core/src/main/java/com/datastrato/gravitino/meta/RoleEntity.java
@@ -156,7 +156,7 @@ public class RoleEntity implements Role, Entity, Auditable, 
HasIdentifier {
 
   @Override
   public int hashCode() {
-    return Objects.hash(id, name, properties, auditInfo, securableObjects);
+    return Objects.hash(id, name, properties, auditInfo, securableObjects, 
namespace);
   }
 
   /**
diff --git a/core/src/test/java/com/datastrato/gravitino/TestCatalog.java 
b/core/src/test/java/com/datastrato/gravitino/TestCatalog.java
index 8d4668865..a732ab0b8 100644
--- a/core/src/test/java/com/datastrato/gravitino/TestCatalog.java
+++ b/core/src/test/java/com/datastrato/gravitino/TestCatalog.java
@@ -115,6 +115,15 @@ public class TestCatalog extends BaseCatalog<TestCatalog> {
                     false,
                     false,
                     false))
+            .put(
+                AUTHORIZATION_PROVIDER,
+                PropertyEntry.stringImmutablePropertyEntry(
+                    Catalog.AUTHORIZATION_PROVIDER,
+                    "The name of the authorization provider for Gravitino",
+                    false,
+                    null,
+                    false,
+                    false))
             .build();
       }
     };
diff --git 
a/core/src/test/java/com/datastrato/gravitino/connector/authorization/TestAuthorization.java
 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/TestAuthorization.java
new file mode 100644
index 000000000..58b25fe20
--- /dev/null
+++ 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/TestAuthorization.java
@@ -0,0 +1,95 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization;
+
+import com.datastrato.gravitino.Catalog;
+import com.datastrato.gravitino.Namespace;
+import com.datastrato.gravitino.TestCatalog;
+import 
com.datastrato.gravitino.connector.authorization.mysql.TestMySQLAuthorizationPlugin;
+import 
com.datastrato.gravitino.connector.authorization.ranger.TestRangerAuthorizationPlugin;
+import com.datastrato.gravitino.meta.AuditInfo;
+import com.datastrato.gravitino.meta.CatalogEntity;
+import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class TestAuthorization {
+  private static TestCatalog hiveCatalog;
+  private static TestCatalog mySQLCatalog;
+
+  @BeforeAll
+  public static void setUp() throws Exception {
+    AuditInfo auditInfo =
+        
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build();
+
+    CatalogEntity hiveCatalogEntity =
+        CatalogEntity.builder()
+            .withId(1L)
+            .withName("catalog-test1")
+            .withNamespace(Namespace.of("default"))
+            .withType(Catalog.Type.RELATIONAL)
+            .withProvider("test")
+            .withAuditInfo(auditInfo)
+            .build();
+
+    hiveCatalog =
+        new TestCatalog()
+            .withCatalogConf(ImmutableMap.of(Catalog.AUTHORIZATION_PROVIDER, 
"ranger"))
+            .withCatalogEntity(hiveCatalogEntity);
+
+    CatalogEntity mySQLEntity =
+        CatalogEntity.builder()
+            .withId(2L)
+            .withName("catalog-test2")
+            .withNamespace(Namespace.of("default"))
+            .withType(Catalog.Type.RELATIONAL)
+            .withProvider("test")
+            .withAuditInfo(auditInfo)
+            .build();
+
+    mySQLCatalog =
+        new TestCatalog()
+            .withCatalogConf(ImmutableMap.of(Catalog.AUTHORIZATION_PROVIDER, 
"mysql"))
+            .withCatalogEntity(mySQLEntity);
+  }
+
+  @Test
+  public void testRangerAuthorization() {
+    AuthorizationPlugin rangerAuthPlugin = 
hiveCatalog.getAuthorizationPlugin();
+    Assertions.assertInstanceOf(TestRangerAuthorizationPlugin.class, 
rangerAuthPlugin);
+    TestRangerAuthorizationPlugin testRangerAuthPlugin =
+        (TestRangerAuthorizationPlugin) rangerAuthPlugin;
+    Assertions.assertFalse(testRangerAuthPlugin.callOnCreateRole1);
+    rangerAuthPlugin.onRoleCreated(null);
+    Assertions.assertTrue(testRangerAuthPlugin.callOnCreateRole1);
+  }
+
+  @Test
+  public void testMySQLAuthorization() {
+    AuthorizationPlugin mySQLAuthPlugin = 
mySQLCatalog.getAuthorizationPlugin();
+    Assertions.assertInstanceOf(TestMySQLAuthorizationPlugin.class, 
mySQLAuthPlugin);
+    TestMySQLAuthorizationPlugin testMySQLAuthPlugin =
+        (TestMySQLAuthorizationPlugin) mySQLAuthPlugin;
+    Assertions.assertFalse(testMySQLAuthPlugin.callOnCreateRole2);
+    mySQLAuthPlugin.onRoleCreated(null);
+    Assertions.assertTrue(testMySQLAuthPlugin.callOnCreateRole2);
+  }
+}
diff --git 
a/core/src/test/java/com/datastrato/gravitino/connector/authorization/mysql/TestMySQLAuthorization.java
 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/mysql/TestMySQLAuthorization.java
new file mode 100644
index 000000000..08eb787fb
--- /dev/null
+++ 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/mysql/TestMySQLAuthorization.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 com.datastrato.gravitino.connector.authorization.mysql;
+
+import com.datastrato.gravitino.connector.authorization.AuthorizationPlugin;
+import com.datastrato.gravitino.connector.authorization.BaseAuthorization;
+
+public class TestMySQLAuthorization extends 
BaseAuthorization<TestMySQLAuthorization> {
+
+  public TestMySQLAuthorization() {}
+
+  @Override
+  public String shortName() {
+    return "mysql";
+  }
+
+  @Override
+  protected AuthorizationPlugin newPlugin() {
+    return new TestMySQLAuthorizationPlugin();
+  }
+}
diff --git 
a/core/src/test/java/com/datastrato/gravitino/connector/authorization/mysql/TestMySQLAuthorizationPlugin.java
 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/mysql/TestMySQLAuthorizationPlugin.java
new file mode 100644
index 000000000..1ab26526d
--- /dev/null
+++ 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/mysql/TestMySQLAuthorizationPlugin.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization.mysql;
+
+import com.datastrato.gravitino.authorization.Group;
+import com.datastrato.gravitino.authorization.Role;
+import com.datastrato.gravitino.authorization.RoleChange;
+import com.datastrato.gravitino.authorization.User;
+import com.datastrato.gravitino.connector.authorization.AuthorizationPlugin;
+import java.io.IOException;
+import java.util.List;
+
+public class TestMySQLAuthorizationPlugin implements AuthorizationPlugin {
+  public boolean callOnCreateRole2 = false;
+
+  @Override
+  public Boolean onRoleCreated(Role role) throws RuntimeException {
+    callOnCreateRole2 = true;
+    return null;
+  }
+
+  @Override
+  public Boolean onRoleAcquired(Role role) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRoleDeleted(Role role) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRoleUpdated(Role role, RoleChange... changes) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onUserAdded(User user) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onUserRemoved(User user) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onUserAcquired(User user) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGroupAdded(Group group) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGroupRemoved(Group group) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGroupAcquired(Group group) {
+    return null;
+  }
+
+  @Override
+  public Boolean onGrantedRolesToUser(List<Role> roles, User user) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRevokedRolesFromUser(List<Role> roles, User user) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGrantedRolesToGroup(List<Role> roles, Group group) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRevokedRolesFromGroup(List<Role> roles, Group group) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public void close() throws IOException {}
+}
diff --git 
a/core/src/test/java/com/datastrato/gravitino/connector/authorization/ranger/TestRangerAuthorization.java
 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/ranger/TestRangerAuthorization.java
new file mode 100644
index 000000000..0f4cf4d85
--- /dev/null
+++ 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/ranger/TestRangerAuthorization.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 com.datastrato.gravitino.connector.authorization.ranger;
+
+import com.datastrato.gravitino.connector.authorization.AuthorizationPlugin;
+import com.datastrato.gravitino.connector.authorization.BaseAuthorization;
+
+public class TestRangerAuthorization extends 
BaseAuthorization<TestRangerAuthorization> {
+
+  public TestRangerAuthorization() {}
+
+  @Override
+  public String shortName() {
+    return "ranger";
+  }
+
+  @Override
+  protected AuthorizationPlugin newPlugin() {
+    return new TestRangerAuthorizationPlugin();
+  }
+}
diff --git 
a/core/src/test/java/com/datastrato/gravitino/connector/authorization/ranger/TestRangerAuthorizationPlugin.java
 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/ranger/TestRangerAuthorizationPlugin.java
new file mode 100644
index 000000000..cf0f29ec2
--- /dev/null
+++ 
b/core/src/test/java/com/datastrato/gravitino/connector/authorization/ranger/TestRangerAuthorizationPlugin.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.datastrato.gravitino.connector.authorization.ranger;
+
+import com.datastrato.gravitino.authorization.Group;
+import com.datastrato.gravitino.authorization.Role;
+import com.datastrato.gravitino.authorization.RoleChange;
+import com.datastrato.gravitino.authorization.User;
+import com.datastrato.gravitino.connector.authorization.AuthorizationPlugin;
+import java.io.IOException;
+import java.util.List;
+
+public class TestRangerAuthorizationPlugin implements AuthorizationPlugin {
+  public boolean callOnCreateRole1 = false;
+
+  @Override
+  public Boolean onRoleCreated(Role role) throws RuntimeException {
+    callOnCreateRole1 = true;
+    return null;
+  }
+
+  @Override
+  public Boolean onRoleAcquired(Role role) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRoleDeleted(Role role) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRoleUpdated(Role role, RoleChange... changes) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onUserAdded(User user) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onUserRemoved(User user) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onUserAcquired(User user) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGroupAdded(Group group) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGroupRemoved(Group group) throws RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGroupAcquired(Group group) {
+    return null;
+  }
+
+  @Override
+  public Boolean onGrantedRolesToUser(List<Role> roles, User user) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRevokedRolesFromUser(List<Role> roles, User user) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onGrantedRolesToGroup(List<Role> roles, Group group) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public Boolean onRevokedRolesFromGroup(List<Role> roles, Group group) throws 
RuntimeException {
+    return null;
+  }
+
+  @Override
+  public void close() throws IOException {}
+}
diff --git 
a/core/src/test/resources/META-INF/services/com.datastrato.gravitino.connector.authorization.AuthorizationProvider
 
b/core/src/test/resources/META-INF/services/com.datastrato.gravitino.connector.authorization.AuthorizationProvider
new file mode 100644
index 000000000..e2f90e557
--- /dev/null
+++ 
b/core/src/test/resources/META-INF/services/com.datastrato.gravitino.connector.authorization.AuthorizationProvider
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+com.datastrato.gravitino.connector.authorization.ranger.TestRangerAuthorization
+com.datastrato.gravitino.connector.authorization.mysql.TestMySQLAuthorization
\ No newline at end of file
diff --git a/integration-test/build.gradle.kts 
b/integration-test/build.gradle.kts
index 016142e0b..e51f72799 100644
--- a/integration-test/build.gradle.kts
+++ b/integration-test/build.gradle.kts
@@ -161,7 +161,7 @@ tasks.test {
       environment("GRAVITINO_CI_TRINO_DOCKER_IMAGE", 
"datastrato/gravitino-ci-trino:0.1.5")
       environment("GRAVITINO_CI_KAFKA_DOCKER_IMAGE", "apache/kafka:3.7.0")
       environment("GRAVITINO_CI_DORIS_DOCKER_IMAGE", 
"datastrato/gravitino-ci-doris:0.1.5")
-      environment("GRAVITINO_CI_RANGER_DOCKER_IMAGE", 
"datastrato/gravitino-ci-ranger:0.1.0")
+      environment("GRAVITINO_CI_RANGER_DOCKER_IMAGE", 
"datastrato/gravitino-ci-ranger:0.1.1")
 
       copy {
         from("${project.rootDir}/dev/docker/trino/conf")
diff --git 
a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/authorization/ranger/RangerIT.java
 
b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/authorization/ranger/RangerIT.java
index 4f0a312c2..4922c26ae 100644
--- 
a/integration-test/src/test/java/com/datastrato/gravitino/integration/test/authorization/ranger/RangerIT.java
+++ 
b/integration-test/src/test/java/com/datastrato/gravitino/integration/test/authorization/ranger/RangerIT.java
@@ -18,9 +18,12 @@
  */
 package com.datastrato.gravitino.integration.test.authorization.ranger;
 
+import static org.apache.ranger.plugin.util.SearchFilter.SERVICE_NAME;
+import static org.apache.ranger.plugin.util.SearchFilter.SERVICE_TYPE;
+
 import com.datastrato.gravitino.integration.test.container.ContainerSuite;
 import com.google.common.collect.ImmutableMap;
-import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.apache.ranger.RangerClient;
@@ -34,8 +37,10 @@ import org.junit.jupiter.api.Test;
 
 @Tag("gravitino-docker-test")
 public class RangerIT {
-  private static final String serviceName = "trino-test";
+  private static final String trinoServiceName = "trinodev";
   private static final String trinoType = "trino";
+  private static final String hiveServiceName = "hivedev";
+  private static final String hiveType = "hive";
   private static RangerClient rangerClient;
 
   private static final ContainerSuite containerSuite = 
ContainerSuite.getInstance();
@@ -50,7 +55,8 @@ public class RangerIT {
   @AfterAll
   public static void cleanup() throws RangerServiceException {
     if (rangerClient != null) {
-      rangerClient.deleteService(serviceName);
+      rangerClient.deleteService(trinoServiceName);
+      rangerClient.deleteService(hiveServiceName);
     }
   }
 
@@ -65,7 +71,7 @@ public class RangerIT {
 
     RangerService service = new RangerService();
     service.setType(trinoType);
-    service.setName(serviceName);
+    service.setName(trinoServiceName);
     service.setConfigs(
         ImmutableMap.<String, String>builder()
             .put(usernameKey, usernameVal)
@@ -76,12 +82,50 @@ public class RangerIT {
     RangerService createdService = rangerClient.createService(service);
     Assertions.assertNotNull(createdService);
 
-    Map<String, String> filter = Collections.emptyMap();
+    Map<String, String> filter = new HashMap<>();
+    filter.put(SERVICE_TYPE, trinoType);
+    filter.put(SERVICE_NAME, trinoServiceName);
     List<RangerService> services = rangerClient.findServices(filter);
-    Assertions.assertEquals(services.get(0).getName(), serviceName);
+    Assertions.assertEquals(services.get(0).getName(), trinoServiceName);
     Assertions.assertEquals(services.get(0).getType(), trinoType);
     Assertions.assertEquals(services.get(0).getConfigs().get(usernameKey), 
usernameVal);
     Assertions.assertEquals(services.get(0).getConfigs().get(jdbcKey), 
jdbcVal);
     Assertions.assertEquals(services.get(0).getConfigs().get(jdbcUrlKey), 
jdbcUrlVal);
   }
+
+  @Test
+  public void createHiveService() throws RangerServiceException {
+    String usernameKey = "username";
+    String usernameVal = "admin";
+    String passwordKey = "password";
+    String passwordVal = "admin";
+    String jdbcKey = "jdbc.driverClassName";
+    String jdbcVal = "org.apache.hive.jdbc.HiveDriver";
+    String jdbcUrlKey = "jdbc.url";
+    String jdbcUrlVal = "jdbc:hive2://172.17.0.2:10000";
+
+    RangerService service = new RangerService();
+    service.setType(hiveType);
+    service.setName(hiveServiceName);
+    service.setConfigs(
+        ImmutableMap.<String, String>builder()
+            .put(usernameKey, usernameVal)
+            .put(passwordKey, passwordVal)
+            .put(jdbcKey, jdbcVal)
+            .put(jdbcUrlKey, jdbcUrlVal)
+            .build());
+
+    RangerService createdService = rangerClient.createService(service);
+    Assertions.assertNotNull(createdService);
+
+    Map<String, String> filter = new HashMap<>();
+    filter.put(SERVICE_TYPE, hiveType);
+    filter.put(SERVICE_NAME, hiveServiceName);
+    List<RangerService> services = rangerClient.findServices(filter);
+    Assertions.assertEquals(services.get(0).getName(), hiveServiceName);
+    Assertions.assertEquals(services.get(0).getType(), hiveType);
+    Assertions.assertEquals(services.get(0).getConfigs().get(usernameKey), 
usernameVal);
+    Assertions.assertEquals(services.get(0).getConfigs().get(jdbcKey), 
jdbcVal);
+    Assertions.assertEquals(services.get(0).getConfigs().get(jdbcUrlKey), 
jdbcUrlVal);
+  }
 }

Reply via email to