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 dab92a5f6 [#4886] feat(server,core): Supports to list roles by object 
(#5023)
dab92a5f6 is described below

commit dab92a5f60c7d01f67250fad5fdc52bcaa2785c5
Author: roryqi <[email protected]>
AuthorDate: Fri Sep 27 15:17:03 2024 +0800

    [#4886] feat(server,core): Supports to list roles by object (#5023)
    
    ### What changes were proposed in this pull request?
    Supports to list roles by object
    
    ### Why are the changes needed?
    
    Fix: #4886
    
    ### Does this PR introduce _any_ user-facing change?
    
    I will add the document later.
    
    ### How was this patch tested?
    
    Add new UT.
---
 .../main/java/org/apache/gravitino/Catalog.java    |   9 +
 .../main/java/org/apache/gravitino/Metalake.java   |   9 +
 api/src/main/java/org/apache/gravitino/Schema.java |   9 +
 .../SupportsRoles.java}                            |  31 +--
 .../java/org/apache/gravitino/file/Fileset.java    |   9 +
 .../java/org/apache/gravitino/messaging/Topic.java |   9 +
 .../main/java/org/apache/gravitino/rel/Table.java  |   9 +
 .../apache/gravitino/client/BaseSchemaCatalog.java |  16 +-
 .../apache/gravitino/client/GenericFileset.java    |  16 +-
 .../org/apache/gravitino/client/GenericSchema.java |  16 +-
 .../org/apache/gravitino/client/GenericTopic.java  |  16 +-
 .../apache/gravitino/client/GravitinoMetalake.java |  19 +-
 .../client/MetadataObjectRoleOperations.java       |  56 +++++
 .../apache/gravitino/client/RelationalTable.java   |  16 +-
 .../apache/gravitino/client/TestSupportRoles.java  | 256 +++++++++++++++++++++
 .../test/authorization/AccessControlIT.java        |  53 +++++
 .../gravitino/SupportsRelationOperations.java      |  23 +-
 .../authorization/AccessControlDispatcher.java     |  14 ++
 .../authorization/AccessControlManager.java        |  10 +-
 .../authorization/FutureGrantManager.java          |   2 +-
 .../gravitino/authorization/RoleManager.java       |  33 +++
 .../hook/AccessControlHookDispatcher.java          |   8 +
 .../gravitino/storage/relational/JDBCBackend.java  |   6 +-
 .../storage/relational/RelationalEntityStore.java  |   6 +-
 .../relational/service/RoleMetaService.java        |  96 ++++----
 .../authorization/TestAccessControlManager.java    |  29 +++
 .../relational/service/TestRoleMetaService.java    |   2 +-
 .../web/rest/MetadataObjectRoleOperations.java     |  89 +++++++
 .../web/rest/TestMetadataObjectRoleOperations.java | 146 ++++++++++++
 .../server/web/rest/TestRoleOperations.java        |  34 +--
 30 files changed, 939 insertions(+), 108 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/Catalog.java 
b/api/src/main/java/org/apache/gravitino/Catalog.java
index 052a04d94..431a798d5 100644
--- a/api/src/main/java/org/apache/gravitino/Catalog.java
+++ b/api/src/main/java/org/apache/gravitino/Catalog.java
@@ -21,6 +21,7 @@ package org.apache.gravitino;
 import java.util.Locale;
 import java.util.Map;
 import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.file.FilesetCatalog;
 import org.apache.gravitino.messaging.TopicCatalog;
 import org.apache.gravitino.rel.TableCatalog;
@@ -181,4 +182,12 @@ public interface Catalog extends Auditable {
   default SupportsTags supportsTags() throws UnsupportedOperationException {
     throw new UnsupportedOperationException("Catalog does not support tag 
operations");
   }
+
+  /**
+   * @return the {@link SupportsRoles} if the catalog supports role operations.
+   * @throws UnsupportedOperationException if the catalog does not support 
role operations.
+   */
+  default SupportsRoles supportsRoles() throws UnsupportedOperationException {
+    throw new UnsupportedOperationException("Catalog does not support role 
operations");
+  }
 }
diff --git a/api/src/main/java/org/apache/gravitino/Metalake.java 
b/api/src/main/java/org/apache/gravitino/Metalake.java
index 6b4ac76ba..fb6fdbee0 100644
--- a/api/src/main/java/org/apache/gravitino/Metalake.java
+++ b/api/src/main/java/org/apache/gravitino/Metalake.java
@@ -20,6 +20,7 @@ package org.apache.gravitino;
 
 import java.util.Map;
 import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.authorization.SupportsRoles;
 
 /**
  * The interface of a metalake. The metalake is the top level entity in the 
Apache Gravitino system,
@@ -50,4 +51,12 @@ public interface Metalake extends Auditable {
    * @return The properties of the metalake.
    */
   Map<String, String> properties();
+
+  /**
+   * @return the {@link SupportsRoles} if the metalake supports role 
operations.
+   * @throws UnsupportedOperationException if the metalake does not support 
role operations.
+   */
+  default SupportsRoles supportsRoles() {
+    throw new UnsupportedOperationException("Metalake does not support role 
operations.");
+  }
 }
diff --git a/api/src/main/java/org/apache/gravitino/Schema.java 
b/api/src/main/java/org/apache/gravitino/Schema.java
index 872b0a25e..7cedf94f6 100644
--- a/api/src/main/java/org/apache/gravitino/Schema.java
+++ b/api/src/main/java/org/apache/gravitino/Schema.java
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.Map;
 import javax.annotation.Nullable;
 import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.tag.SupportsTags;
 
 /**
@@ -56,4 +57,12 @@ public interface Schema extends Auditable {
   default SupportsTags supportsTags() {
     throw new UnsupportedOperationException("Schema does not support tag 
operations.");
   }
+
+  /**
+   * @return the {@link SupportsRoles} if the schema supports role operations.
+   * @throws UnsupportedOperationException if the schema does not support role 
operations.
+   */
+  default SupportsRoles supportsRoles() {
+    throw new UnsupportedOperationException("Schema does not support role 
operations.");
+  }
 }
diff --git a/api/src/main/java/org/apache/gravitino/Metalake.java 
b/api/src/main/java/org/apache/gravitino/authorization/SupportsRoles.java
similarity index 55%
copy from api/src/main/java/org/apache/gravitino/Metalake.java
copy to api/src/main/java/org/apache/gravitino/authorization/SupportsRoles.java
index 6b4ac76ba..e83a7e20e 100644
--- a/api/src/main/java/org/apache/gravitino/Metalake.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/SupportsRoles.java
@@ -16,38 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.gravitino;
+package org.apache.gravitino.authorization;
 
-import java.util.Map;
 import org.apache.gravitino.annotation.Evolving;
 
 /**
- * The interface of a metalake. The metalake is the top level entity in the 
Apache Gravitino system,
- * containing a set of catalogs.
+ * Interface for supporting list role names for objects. This interface will 
be mixed with metadata
+ * objects to provide listing role operations.
  */
 @Evolving
-public interface Metalake extends Auditable {
+public interface SupportsRoles {
 
   /**
-   * The name of the metalake.
+   * List all the role names associated with this metadata object.
    *
-   * @return The name of the metalake.
+   * @return The role name list associated with this metadata object.
    */
-  String name();
-
-  /**
-   * The comment of the metalake. Note. this method will return null if the 
comment is not set for
-   * this metalake.
-   *
-   * @return The comment of the metalake.
-   */
-  String comment();
-
-  /**
-   * The properties of the metalake. Note, this method will return null if the 
properties are not
-   * set.
-   *
-   * @return The properties of the metalake.
-   */
-  Map<String, String> properties();
+  String[] listBindingRoleNames();
 }
diff --git a/api/src/main/java/org/apache/gravitino/file/Fileset.java 
b/api/src/main/java/org/apache/gravitino/file/Fileset.java
index ccff039da..97afcc650 100644
--- a/api/src/main/java/org/apache/gravitino/file/Fileset.java
+++ b/api/src/main/java/org/apache/gravitino/file/Fileset.java
@@ -24,6 +24,7 @@ import javax.annotation.Nullable;
 import org.apache.gravitino.Auditable;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.tag.SupportsTags;
 
 /**
@@ -114,4 +115,12 @@ public interface Fileset extends Auditable {
   default SupportsTags supportsTags() {
     throw new UnsupportedOperationException("Fileset does not support tag 
operations.");
   }
+
+  /**
+   * @return The {@link SupportsRoles} if the fileset supports role operations.
+   * @throws UnsupportedOperationException If the fileset does not support 
role operations.
+   */
+  default SupportsRoles supportsRoles() {
+    throw new UnsupportedOperationException("Fileset does not support role 
operations.");
+  }
 }
diff --git a/api/src/main/java/org/apache/gravitino/messaging/Topic.java 
b/api/src/main/java/org/apache/gravitino/messaging/Topic.java
index 78607f486..7162c45d2 100644
--- a/api/src/main/java/org/apache/gravitino/messaging/Topic.java
+++ b/api/src/main/java/org/apache/gravitino/messaging/Topic.java
@@ -24,6 +24,7 @@ import javax.annotation.Nullable;
 import org.apache.gravitino.Auditable;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.tag.SupportsTags;
 
 /**
@@ -58,4 +59,12 @@ public interface Topic extends Auditable {
   default SupportsTags supportsTags() {
     throw new UnsupportedOperationException("Topic does not support tag 
operations.");
   }
+
+  /**
+   * @return the {@link SupportsRoles} if the topic supports role operations.
+   * @throws UnsupportedOperationException if the topic does not support role 
operations.
+   */
+  default SupportsRoles supportsRoles() {
+    throw new UnsupportedOperationException("Topic does not support role 
operations.");
+  }
 }
diff --git a/api/src/main/java/org/apache/gravitino/rel/Table.java 
b/api/src/main/java/org/apache/gravitino/rel/Table.java
index c6bafb97a..8bb9e3c12 100644
--- a/api/src/main/java/org/apache/gravitino/rel/Table.java
+++ b/api/src/main/java/org/apache/gravitino/rel/Table.java
@@ -24,6 +24,7 @@ import javax.annotation.Nullable;
 import org.apache.gravitino.Auditable;
 import org.apache.gravitino.Namespace;
 import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.rel.expressions.distributions.Distribution;
 import org.apache.gravitino.rel.expressions.distributions.Distributions;
 import org.apache.gravitino.rel.expressions.sorts.SortOrder;
@@ -103,4 +104,12 @@ public interface Table extends Auditable {
   default SupportsTags supportsTags() {
     throw new UnsupportedOperationException("Table does not support tag 
operations.");
   }
+
+  /**
+   * @return The {@link SupportsRoles} if the table supports role operations.
+   * @throws UnsupportedOperationException If the table does not support role 
operations.
+   */
+  default SupportsRoles supportsRoles() {
+    throw new UnsupportedOperationException("Table does not support role 
operations.");
+  }
 }
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
index 7d46af3a5..9359ea439 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
@@ -31,6 +31,7 @@ import org.apache.gravitino.Namespace;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.SchemaChange;
 import org.apache.gravitino.SupportsSchemas;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.CatalogDTO;
 import org.apache.gravitino.dto.requests.SchemaCreateRequest;
@@ -53,7 +54,7 @@ import org.apache.gravitino.tag.Tag;
  * create, load, alter and drop a schema with specified identifier.
  */
 abstract class BaseSchemaCatalog extends CatalogDTO
-    implements Catalog, SupportsSchemas, SupportsTags {
+    implements Catalog, SupportsSchemas, SupportsTags, SupportsRoles {
   /** The REST client to send the requests. */
   protected final RESTClient restClient;
 
@@ -61,6 +62,7 @@ abstract class BaseSchemaCatalog extends CatalogDTO
   private final Namespace catalogNamespace;
 
   private final MetadataObjectTagOperations objectTagOperations;
+  private final MetadataObjectRoleOperations objectRoleOperations;
 
   BaseSchemaCatalog(
       Namespace catalogNamespace,
@@ -84,6 +86,8 @@ abstract class BaseSchemaCatalog extends CatalogDTO
         MetadataObjects.of(null, this.name(), MetadataObject.Type.CATALOG);
     this.objectTagOperations =
         new MetadataObjectTagOperations(catalogNamespace.level(0), 
metadataObject, restClient);
+    this.objectRoleOperations =
+        new MetadataObjectRoleOperations(catalogNamespace.level(0), 
metadataObject, restClient);
   }
 
   @Override
@@ -96,6 +100,11 @@ abstract class BaseSchemaCatalog extends CatalogDTO
     return this;
   }
 
+  @Override
+  public SupportsRoles supportsRoles() throws UnsupportedOperationException {
+    return this;
+  }
+
   /**
    * List all the schemas under the given catalog namespace.
    *
@@ -239,6 +248,11 @@ abstract class BaseSchemaCatalog extends CatalogDTO
     return objectTagOperations.associateTags(tagsToAdd, tagsToRemove);
   }
 
+  @Override
+  public String[] listBindingRoleNames() {
+    return objectRoleOperations.listBindingRoleNames();
+  }
+
   /**
    * Get the namespace of the current catalog, which is "metalake".
    *
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
index 32e1d7392..68eda6985 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
@@ -26,6 +26,7 @@ import org.apache.gravitino.Audit;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.dto.file.FilesetDTO;
 import org.apache.gravitino.exceptions.NoSuchTagException;
 import org.apache.gravitino.file.Fileset;
@@ -33,11 +34,12 @@ import org.apache.gravitino.tag.SupportsTags;
 import org.apache.gravitino.tag.Tag;
 
 /** Represents a generic fileset. */
-class GenericFileset implements Fileset, SupportsTags {
+class GenericFileset implements Fileset, SupportsTags, SupportsRoles {
 
   private final FilesetDTO filesetDTO;
 
   private final MetadataObjectTagOperations objectTagOperations;
+  private final MetadataObjectRoleOperations objectRoleOperations;
 
   GenericFileset(FilesetDTO filesetDTO, RESTClient restClient, Namespace 
filesetNs) {
     this.filesetDTO = filesetDTO;
@@ -46,6 +48,8 @@ class GenericFileset implements Fileset, SupportsTags {
     MetadataObject filesetObject = MetadataObjects.of(filesetFullName, 
MetadataObject.Type.FILESET);
     this.objectTagOperations =
         new MetadataObjectTagOperations(filesetNs.level(0), filesetObject, 
restClient);
+    this.objectRoleOperations =
+        new MetadataObjectRoleOperations(filesetNs.level(0), filesetObject, 
restClient);
   }
 
   @Override
@@ -84,6 +88,11 @@ class GenericFileset implements Fileset, SupportsTags {
     return this;
   }
 
+  @Override
+  public SupportsRoles supportsRoles() {
+    return this;
+  }
+
   @Override
   public String[] listTags() {
     return objectTagOperations.listTags();
@@ -104,6 +113,11 @@ class GenericFileset implements Fileset, SupportsTags {
     return objectTagOperations.associateTags(tagsToAdd, tagsToRemove);
   }
 
+  @Override
+  public String[] listBindingRoleNames() {
+    return objectRoleOperations.listBindingRoleNames();
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (this == obj) {
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
index e595a53ab..22af2e3a2 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
@@ -23,23 +23,27 @@ import org.apache.gravitino.Audit;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.Schema;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.dto.SchemaDTO;
 import org.apache.gravitino.exceptions.NoSuchTagException;
 import org.apache.gravitino.tag.SupportsTags;
 import org.apache.gravitino.tag.Tag;
 
 /** Represents a generic schema. */
-class GenericSchema implements Schema, SupportsTags {
+class GenericSchema implements Schema, SupportsTags, SupportsRoles {
 
   private final SchemaDTO schemaDTO;
 
   private final MetadataObjectTagOperations objectTagOperations;
+  private final MetadataObjectRoleOperations objectRoleOperations;
 
   GenericSchema(SchemaDTO schemaDTO, RESTClient restClient, String metalake, 
String catalog) {
     this.schemaDTO = schemaDTO;
     MetadataObject schemaObject =
         MetadataObjects.of(catalog, schemaDTO.name(), 
MetadataObject.Type.SCHEMA);
     this.objectTagOperations = new MetadataObjectTagOperations(metalake, 
schemaObject, restClient);
+    this.objectRoleOperations =
+        new MetadataObjectRoleOperations(metalake, schemaObject, restClient);
   }
 
   @Override
@@ -47,6 +51,11 @@ class GenericSchema implements Schema, SupportsTags {
     return this;
   }
 
+  @Override
+  public SupportsRoles supportsRoles() {
+    return this;
+  }
+
   @Override
   public String name() {
     return schemaDTO.name();
@@ -87,6 +96,11 @@ class GenericSchema implements Schema, SupportsTags {
     return objectTagOperations.associateTags(tagsToAdd, tagsToRemove);
   }
 
+  @Override
+  public String[] listBindingRoleNames() {
+    return objectRoleOperations.listBindingRoleNames();
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (this == obj) {
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
index 55edfdd54..0048d489c 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
@@ -25,6 +25,7 @@ import org.apache.gravitino.Audit;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.dto.messaging.TopicDTO;
 import org.apache.gravitino.exceptions.NoSuchTagException;
 import org.apache.gravitino.messaging.Topic;
@@ -32,11 +33,12 @@ import org.apache.gravitino.tag.SupportsTags;
 import org.apache.gravitino.tag.Tag;
 
 /** Represents a generic topic. */
-class GenericTopic implements Topic, SupportsTags {
+class GenericTopic implements Topic, SupportsTags, SupportsRoles {
 
   private final TopicDTO topicDTO;
 
   private final MetadataObjectTagOperations objectTagOperations;
+  private final MetadataObjectRoleOperations objectRoleOperations;
 
   GenericTopic(TopicDTO topicDTO, RESTClient restClient, Namespace topicNs) {
     this.topicDTO = topicDTO;
@@ -45,6 +47,8 @@ class GenericTopic implements Topic, SupportsTags {
     MetadataObject topicObject = MetadataObjects.of(topicFullName, 
MetadataObject.Type.TOPIC);
     this.objectTagOperations =
         new MetadataObjectTagOperations(topicNs.level(0), topicObject, 
restClient);
+    this.objectRoleOperations =
+        new MetadataObjectRoleOperations(topicNs.level(0), topicObject, 
restClient);
   }
 
   @Override
@@ -72,6 +76,11 @@ class GenericTopic implements Topic, SupportsTags {
     return this;
   }
 
+  @Override
+  public SupportsRoles supportsRoles() {
+    return this;
+  }
+
   @Override
   public String[] listTags() {
     return objectTagOperations.listTags();
@@ -92,6 +101,11 @@ class GenericTopic implements Topic, SupportsTags {
     return objectTagOperations.associateTags(tagsToAdd, tagsToRemove);
   }
 
+  @Override
+  public String[] listBindingRoleNames() {
+    return objectRoleOperations.listBindingRoleNames();
+  }
+
   @Override
   public boolean equals(Object obj) {
     if (this == obj) {
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
index 58973b4cf..8f98b6fd3 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
@@ -32,12 +32,14 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.SupportsCatalogs;
 import org.apache.gravitino.authorization.Group;
 import org.apache.gravitino.authorization.Owner;
 import org.apache.gravitino.authorization.Role;
 import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.authorization.User;
 import org.apache.gravitino.dto.AuditDTO;
 import org.apache.gravitino.dto.MetalakeDTO;
@@ -93,7 +95,8 @@ import org.apache.gravitino.tag.TagOperations;
  * catalogs as sub-level metadata collections. With {@link GravitinoMetalake}, 
users can list,
  * create, load, alter and drop a catalog with specified identifier.
  */
-public class GravitinoMetalake extends MetalakeDTO implements 
SupportsCatalogs, TagOperations {
+public class GravitinoMetalake extends MetalakeDTO
+    implements SupportsCatalogs, TagOperations, SupportsRoles {
   private static final String API_METALAKES_CATALOGS_PATH = 
"api/metalakes/%s/catalogs/%s";
   private static final String API_PERMISSION_PATH = 
"api/metalakes/%s/permissions/%s";
   private static final String API_METALAKES_USERS_PATH = 
"api/metalakes/%s/users/%s";
@@ -105,6 +108,7 @@ public class GravitinoMetalake extends MetalakeDTO 
implements SupportsCatalogs,
   private static final String BLANK_PLACEHOLDER = "";
 
   private final RESTClient restClient;
+  private final MetadataObjectRoleOperations metadataObjectRoleOperations;
 
   GravitinoMetalake(
       String name,
@@ -114,6 +118,9 @@ public class GravitinoMetalake extends MetalakeDTO 
implements SupportsCatalogs,
       RESTClient restClient) {
     super(name, comment, properties, auditDTO);
     this.restClient = restClient;
+    this.metadataObjectRoleOperations =
+        new MetadataObjectRoleOperations(
+            name, MetadataObjects.of(null, name, 
MetadataObject.Type.METALAKE), restClient);
   }
 
   /**
@@ -308,6 +315,11 @@ public class GravitinoMetalake extends MetalakeDTO 
implements SupportsCatalogs,
     ErrorHandlers.catalogErrorHandler().accept(resp);
   }
 
+  @Override
+  public SupportsRoles supportsRoles() {
+    return this;
+  }
+
   /*
    * List all the tag names under a metalake.
    *
@@ -896,6 +908,11 @@ public class GravitinoMetalake extends MetalakeDTO 
implements SupportsCatalogs,
     resp.validate();
   }
 
+  @Override
+  public String[] listBindingRoleNames() {
+    return metadataObjectRoleOperations.listBindingRoleNames();
+  }
+
   static class Builder extends MetalakeDTO.Builder<Builder> {
     private RESTClient restClient;
 
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectRoleOperations.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectRoleOperations.java
new file mode 100644
index 000000000..54a663435
--- /dev/null
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectRoleOperations.java
@@ -0,0 +1,56 @@
+/*
+ * 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.client;
+
+import java.util.Collections;
+import java.util.Locale;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.authorization.SupportsRoles;
+import org.apache.gravitino.dto.responses.NameListResponse;
+
+class MetadataObjectRoleOperations implements SupportsRoles {
+
+  private final RESTClient restClient;
+
+  private final String roleRequestPath;
+
+  MetadataObjectRoleOperations(
+      String metalakeName, MetadataObject metadataObject, RESTClient 
restClient) {
+    this.restClient = restClient;
+    this.roleRequestPath =
+        String.format(
+            "api/metalakes/%s/objects/%s/%s/roles",
+            metalakeName,
+            metadataObject.type().name().toLowerCase(Locale.ROOT),
+            metadataObject.fullName());
+  }
+
+  @Override
+  public String[] listBindingRoleNames() {
+    NameListResponse resp =
+        restClient.get(
+            roleRequestPath,
+            NameListResponse.class,
+            Collections.emptyMap(),
+            ErrorHandlers.roleErrorHandler());
+    resp.validate();
+
+    return resp.getNames();
+  }
+}
diff --git 
a/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
 
b/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
index af7e094b1..83634295f 100644
--- 
a/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
+++ 
b/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
@@ -32,6 +32,7 @@ import org.apache.gravitino.Audit;
 import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.MetadataObjects;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.authorization.SupportsRoles;
 import org.apache.gravitino.dto.rel.TableDTO;
 import org.apache.gravitino.dto.rel.partitions.PartitionDTO;
 import org.apache.gravitino.dto.requests.AddPartitionsRequest;
@@ -55,7 +56,7 @@ import org.apache.gravitino.tag.SupportsTags;
 import org.apache.gravitino.tag.Tag;
 
 /** Represents a relational table. */
-class RelationalTable implements Table, SupportsPartitions, SupportsTags {
+class RelationalTable implements Table, SupportsPartitions, SupportsTags, 
SupportsRoles {
 
   private static final Joiner DOT_JOINER = Joiner.on(".");
 
@@ -66,6 +67,7 @@ class RelationalTable implements Table, SupportsPartitions, 
SupportsTags {
   private final Namespace namespace;
 
   private final MetadataObjectTagOperations objectTagOperations;
+  private final MetadataObjectRoleOperations objectRoleOperations;
 
   /**
    * Creates a new RelationalTable.
@@ -94,6 +96,8 @@ class RelationalTable implements Table, SupportsPartitions, 
SupportsTags {
         MetadataObjects.parse(tableFullName(namespace, tableDTO.name()), 
MetadataObject.Type.TABLE);
     this.objectTagOperations =
         new MetadataObjectTagOperations(namespace.level(0), tableObject, 
restClient);
+    this.objectRoleOperations =
+        new MetadataObjectRoleOperations(namespace.level(0), tableObject, 
restClient);
   }
 
   /**
@@ -284,6 +288,11 @@ class RelationalTable implements Table, 
SupportsPartitions, SupportsTags {
     return this;
   }
 
+  @Override
+  public SupportsRoles supportsRoles() {
+    return this;
+  }
+
   private static String tableFullName(Namespace tableNS, String tableName) {
     return DOT_JOINER.join(tableNS.level(1), tableNS.level(2), tableName);
   }
@@ -307,4 +316,9 @@ class RelationalTable implements Table, SupportsPartitions, 
SupportsTags {
   public String[] associateTags(String[] tagsToAdd, String[] tagsToRemove) {
     return objectTagOperations.associateTags(tagsToAdd, tagsToRemove);
   }
+
+  @Override
+  public String[] listBindingRoleNames() {
+    return objectRoleOperations.listBindingRoleNames();
+  }
 }
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportRoles.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportRoles.java
new file mode 100644
index 000000000..b22d5b21b
--- /dev/null
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportRoles.java
@@ -0,0 +1,256 @@
+/*
+ * 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.client;
+
+import static org.apache.hc.core5.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
+import static org.apache.hc.core5.http.HttpStatus.SC_NOT_FOUND;
+import static org.apache.hc.core5.http.HttpStatus.SC_OK;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Collections;
+import java.util.Locale;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.Metalake;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.Schema;
+import org.apache.gravitino.authorization.SupportsRoles;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.dto.SchemaDTO;
+import org.apache.gravitino.dto.file.FilesetDTO;
+import org.apache.gravitino.dto.messaging.TopicDTO;
+import org.apache.gravitino.dto.rel.ColumnDTO;
+import org.apache.gravitino.dto.rel.TableDTO;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.exceptions.NotFoundException;
+import org.apache.gravitino.file.Fileset;
+import org.apache.gravitino.messaging.Topic;
+import org.apache.gravitino.rel.Table;
+import org.apache.gravitino.rel.types.Types;
+import org.apache.hc.core5.http.Method;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class TestSupportRoles extends TestBase {
+  private static final String METALAKE_NAME = "metalake";
+
+  private static Catalog relationalCatalog;
+
+  private static Catalog filesetCatalog;
+
+  private static Catalog messagingCatalog;
+
+  private static Schema genericSchema;
+
+  private static Table relationalTable;
+
+  private static Fileset genericFileset;
+
+  private static Topic genericTopic;
+  private static Metalake metalake;
+
+  @BeforeAll
+  public static void setUp() throws Exception {
+    TestBase.setUp();
+    metalake = TestGravitinoMetalake.createMetalake(client, METALAKE_NAME);
+
+    relationalCatalog =
+        new RelationalCatalog(
+            Namespace.of(METALAKE_NAME),
+            "catalog1",
+            Catalog.Type.RELATIONAL,
+            "test",
+            "comment",
+            Collections.emptyMap(),
+            AuditDTO.builder().build(),
+            client.restClient());
+
+    filesetCatalog =
+        new FilesetCatalog(
+            Namespace.of(METALAKE_NAME),
+            "catalog2",
+            Catalog.Type.FILESET,
+            "test",
+            "comment",
+            Collections.emptyMap(),
+            AuditDTO.builder().build(),
+            client.restClient());
+
+    messagingCatalog =
+        new MessagingCatalog(
+            Namespace.of(METALAKE_NAME),
+            "catalog3",
+            Catalog.Type.MESSAGING,
+            "test",
+            "comment",
+            Collections.emptyMap(),
+            AuditDTO.builder().build(),
+            client.restClient());
+
+    genericSchema =
+        new GenericSchema(
+            SchemaDTO.builder()
+                .withName("schema1")
+                .withComment("comment1")
+                .withProperties(Collections.emptyMap())
+                .withAudit(AuditDTO.builder().withCreator("test").build())
+                .build(),
+            client.restClient(),
+            METALAKE_NAME,
+            "catalog1");
+
+    relationalTable =
+        RelationalTable.from(
+            Namespace.of(METALAKE_NAME, "catalog1", "schema1"),
+            TableDTO.builder()
+                .withName("table1")
+                .withComment("comment1")
+                .withColumns(
+                    new ColumnDTO[] {
+                      ColumnDTO.builder()
+                          .withName("col1")
+                          .withDataType(Types.IntegerType.get())
+                          .build()
+                    })
+                .withProperties(Collections.emptyMap())
+                .withAudit(AuditDTO.builder().withCreator("test").build())
+                .build(),
+            client.restClient());
+
+    genericFileset =
+        new GenericFileset(
+            FilesetDTO.builder()
+                .name("fileset1")
+                .comment("comment1")
+                .type(Fileset.Type.EXTERNAL)
+                .storageLocation("s3://bucket/path")
+                .properties(Collections.emptyMap())
+                .audit(AuditDTO.builder().withCreator("test").build())
+                .build(),
+            client.restClient(),
+            Namespace.of(METALAKE_NAME, "catalog1", "schema1"));
+
+    genericTopic =
+        new GenericTopic(
+            TopicDTO.builder()
+                .withName("topic1")
+                .withComment("comment1")
+                .withProperties(Collections.emptyMap())
+                .withAudit(AuditDTO.builder().withCreator("test").build())
+                .build(),
+            client.restClient(),
+            Namespace.of(METALAKE_NAME, "catalog1", "schema1"));
+  }
+
+  @Test
+  public void testListRolesForMetalake() throws JsonProcessingException {
+    testListRoles(
+        metalake.supportsRoles(),
+        MetadataObjects.of(null, metalake.name(), 
MetadataObject.Type.METALAKE));
+  }
+
+  @Test
+  public void testListRolesForCatalog() throws JsonProcessingException {
+    testListRoles(
+        relationalCatalog.supportsRoles(),
+        MetadataObjects.of(null, relationalCatalog.name(), 
MetadataObject.Type.CATALOG));
+
+    testListRoles(
+        filesetCatalog.supportsRoles(),
+        MetadataObjects.of(null, filesetCatalog.name(), 
MetadataObject.Type.CATALOG));
+
+    testListRoles(
+        messagingCatalog.supportsRoles(),
+        MetadataObjects.of(null, messagingCatalog.name(), 
MetadataObject.Type.CATALOG));
+  }
+
+  @Test
+  public void testListRolesForSchema() throws JsonProcessingException {
+    testListRoles(
+        genericSchema.supportsRoles(),
+        MetadataObjects.of("catalog1", genericSchema.name(), 
MetadataObject.Type.SCHEMA));
+  }
+
+  @Test
+  public void testListRolesForTable() throws JsonProcessingException {
+    testListRoles(
+        relationalTable.supportsRoles(),
+        MetadataObjects.of("catalog1.schema1", relationalTable.name(), 
MetadataObject.Type.TABLE));
+  }
+
+  @Test
+  public void testListRolesForFileset() throws JsonProcessingException {
+    testListRoles(
+        genericFileset.supportsRoles(),
+        MetadataObjects.of("catalog1.schema1", genericFileset.name(), 
MetadataObject.Type.FILESET));
+  }
+
+  @Test
+  public void testListRolesForTopic() throws JsonProcessingException {
+    testListRoles(
+        genericTopic.supportsRoles(),
+        MetadataObjects.of("catalog1.schema1", genericTopic.name(), 
MetadataObject.Type.TOPIC));
+  }
+
+  private void testListRoles(SupportsRoles supportsRoles, MetadataObject 
metadataObject)
+      throws JsonProcessingException {
+    String path =
+        "/api/metalakes/"
+            + METALAKE_NAME
+            + "/objects/"
+            + metadataObject.type().name().toLowerCase(Locale.ROOT)
+            + "/"
+            + metadataObject.fullName()
+            + "/roles";
+
+    String[] roles = new String[] {"role1", "role2"};
+    NameListResponse resp = new NameListResponse(roles);
+    buildMockResource(Method.GET, path, null, resp, SC_OK);
+
+    String[] actualTags = supportsRoles.listBindingRoleNames();
+    Assertions.assertArrayEquals(roles, actualTags);
+
+    // Return empty list
+    NameListResponse resp1 = new NameListResponse(new String[0]);
+    buildMockResource(Method.GET, path, null, resp1, SC_OK);
+
+    String[] actualRoles1 = supportsRoles.listBindingRoleNames();
+    Assertions.assertArrayEquals(new String[0], actualRoles1);
+
+    // Test throw NotFoundException
+    ErrorResponse errorResp =
+        ErrorResponse.notFound(NotFoundException.class.getSimpleName(), "mock 
error");
+    buildMockResource(Method.GET, path, null, errorResp, SC_NOT_FOUND);
+
+    Throwable ex =
+        Assertions.assertThrows(NotFoundException.class, 
supportsRoles::listBindingRoleNames);
+    Assertions.assertTrue(ex.getMessage().contains("mock error"));
+
+    // Test throw internal error
+    ErrorResponse errorResp1 = ErrorResponse.internalError("mock error");
+    buildMockResource(Method.GET, path, null, errorResp1, 
SC_INTERNAL_SERVER_ERROR);
+
+    Throwable ex1 =
+        Assertions.assertThrows(RuntimeException.class, 
supportsRoles::listBindingRoleNames);
+    Assertions.assertTrue(ex1.getMessage().contains("mock error"));
+  }
+}
diff --git 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java
 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java
index 965c31fdf..d9b85bf0d 100644
--- 
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java
+++ 
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java
@@ -26,7 +26,10 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.gravitino.Catalog;
 import org.apache.gravitino.Configs;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Schema;
 import org.apache.gravitino.auth.AuthConstants;
 import org.apache.gravitino.authorization.Group;
 import org.apache.gravitino.authorization.Privilege;
@@ -42,6 +45,7 @@ import 
org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
 import org.apache.gravitino.exceptions.NoSuchRoleException;
 import org.apache.gravitino.exceptions.NoSuchUserException;
 import org.apache.gravitino.exceptions.UserAlreadyExistsException;
+import org.apache.gravitino.file.Fileset;
 import org.apache.gravitino.integration.test.util.AbstractIT;
 import org.apache.gravitino.utils.RandomNameUtils;
 import org.junit.jupiter.api.Assertions;
@@ -61,6 +65,15 @@ public class AccessControlIT extends AbstractIT {
     registerCustomConfigs(configs);
     AbstractIT.startIntegrationTest();
     metalake = client.createMetalake(metalakeName, "metalake comment", 
Collections.emptyMap());
+
+    Catalog filesetCatalog =
+        metalake.createCatalog(
+            "fileset_catalog", Catalog.Type.FILESET, "hadoop", "comment", 
Collections.emptyMap());
+    NameIdentifier fileIdent = NameIdentifier.of("fileset_schema", "fileset");
+    filesetCatalog.asSchemas().createSchema("fileset_schema", "comment", 
Collections.emptyMap());
+    filesetCatalog
+        .asFilesetCatalog()
+        .createFileset(fileIdent, "comment", Fileset.Type.EXTERNAL, "tmp", 
Collections.emptyMap());
   }
 
   @Test
@@ -187,6 +200,46 @@ public class AccessControlIT extends AbstractIT {
     Assertions.assertEquals(
         Lists.newArrayList(anotherRoleName, roleName), 
Arrays.asList(roleNames));
 
+    // List roles by the object (metalake)
+    roleNames = metalake.listBindingRoleNames();
+    Arrays.sort(roleNames);
+    Assertions.assertEquals(
+        Lists.newArrayList(anotherRoleName, roleName), 
Arrays.asList(roleNames));
+
+    String testObjectRole = "testObjectRole";
+    SecurableObject anotherCatalogObject =
+        SecurableObjects.ofCatalog(
+            "fileset_catalog", 
Lists.newArrayList(Privileges.UseCatalog.allow()));
+    SecurableObject schemaObject =
+        SecurableObjects.ofSchema(
+            anotherCatalogObject,
+            "fileset_schema",
+            Lists.newArrayList(Privileges.UseSchema.allow()));
+    SecurableObject filesetObject =
+        SecurableObjects.ofFileset(
+            schemaObject, "fileset", 
Lists.newArrayList(Privileges.ReadFileset.allow()));
+
+    metalake.createRole(
+        testObjectRole,
+        properties,
+        Lists.newArrayList(anotherCatalogObject, schemaObject, filesetObject));
+
+    // List roles by the object (catalog)
+    Catalog catalog = metalake.loadCatalog("fileset_catalog");
+    roleNames = catalog.supportsRoles().listBindingRoleNames();
+    Assertions.assertEquals(Lists.newArrayList(testObjectRole), 
Arrays.asList(roleNames));
+
+    // List roles by the object (schema)
+    Schema schema = catalog.asSchemas().loadSchema("fileset_schema");
+    roleNames = schema.supportsRoles().listBindingRoleNames();
+    Assertions.assertEquals(Lists.newArrayList(testObjectRole), 
Arrays.asList(roleNames));
+
+    // List roles by the object (fileset)
+    Fileset fileset =
+        
catalog.asFilesetCatalog().loadFileset(NameIdentifier.of("fileset_schema", 
"fileset"));
+    roleNames = fileset.supportsRoles().listBindingRoleNames();
+    Assertions.assertEquals(Lists.newArrayList(testObjectRole), 
Arrays.asList(roleNames));
+
     // Verify the object
     Assertions.assertEquals(1, role.securableObjects().size());
     createdObject = role.securableObjects().get(0);
diff --git 
a/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java 
b/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java
index 617f72ab9..d203b94de 100644
--- a/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java
+++ b/core/src/main/java/org/apache/gravitino/SupportsRelationOperations.java
@@ -40,7 +40,7 @@ public interface SupportsRelationOperations {
   }
 
   /**
-   * List the entities according to a give entity in a specific relation.
+   * List the entities according to a given entity in a specific relation.
    *
    * @param relType The type of relation.
    * @param nameIdentifier The given entity identifier
@@ -48,8 +48,27 @@ public interface SupportsRelationOperations {
    * @return The list of entities
    * @throws IOException When occurs storage issues, it will throw IOException.
    */
+  default <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
+      Type relType, NameIdentifier nameIdentifier, Entity.EntityType 
identType) throws IOException {
+    return listEntitiesByRelation(relType, nameIdentifier, identType, true /* 
allFields*/);
+  }
+
+  /**
+   * List the entities according to a given entity in a specific relation.
+   *
+   * @param relType The type of relation.
+   * @param nameIdentifier The given entity identifier
+   * @param identType The given entity type.
+   * @param allFields Some fields may have a relatively high acquisition cost, 
EntityStore provide
+   *     an optional setting to avoid fetching these high-cost fields to 
improve the performance. If
+   *     true, the method will fetch all the fields, Otherwise, the method 
will fetch all the fields
+   *     except for high-cost fields.
+   * @return The list of entities
+   * @throws IOException When occurs storage issues, it will throw IOException.
+   */
   <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
-      Type relType, NameIdentifier nameIdentifier, Entity.EntityType 
identType) throws IOException;
+      Type relType, NameIdentifier nameIdentifier, Entity.EntityType 
identType, boolean allFields)
+      throws IOException;
 
   /**
    * insert a relation between two entities
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
 
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
index 95cb304de..3214c187f 100644
--- 
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
@@ -20,8 +20,10 @@ package org.apache.gravitino.authorization;
 
 import java.util.List;
 import java.util.Map;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchGroupException;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchRoleException;
 import org.apache.gravitino.exceptions.NoSuchUserException;
@@ -246,4 +248,16 @@ public interface AccessControlDispatcher {
    * @throws NoSuchMetalakeException If the Metalake with the given name does 
not exist.
    */
   String[] listRoleNames(String metalake) throws NoSuchMetalakeException;
+
+  /**
+   * Lists the role names associated the metadata object.
+   *
+   * @param metalake The Metalake of the Role.
+   * @return The role list.
+   * @throws NoSuchMetalakeException If the Metalake with the given name does 
not exist.
+   * @throws NoSuchMetadataObjectException If the Metadata object with the 
given name does not
+   *     exist.
+   */
+  String[] listRoleNamesByObject(String metalake, MetadataObject object)
+      throws NoSuchMetalakeException, NoSuchMetadataObjectException;
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
 
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
index 8872afade..c2f2976aa 100644
--- 
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
+++ 
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
@@ -18,14 +18,15 @@
  */
 package org.apache.gravitino.authorization;
 
-import com.google.common.annotations.VisibleForTesting;
 import java.util.List;
 import java.util.Map;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.Configs;
 import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchGroupException;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchRoleException;
 import org.apache.gravitino.exceptions.NoSuchUserException;
@@ -148,8 +149,9 @@ public class AccessControlManager implements 
AccessControlDispatcher {
     return roleManager.listRoleNames(metalake);
   }
 
-  @VisibleForTesting
-  RoleManager getRoleManager() {
-    return roleManager;
+  @Override
+  public String[] listRoleNamesByObject(String metalake, MetadataObject object)
+      throws NoSuchMetalakeException, NoSuchMetadataObjectException {
+    return roleManager.listRoleNamesByObject(metalake, object);
   }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/FutureGrantManager.java 
b/core/src/main/java/org/apache/gravitino/authorization/FutureGrantManager.java
index c24817ea5..b838e1956 100644
--- 
a/core/src/main/java/org/apache/gravitino/authorization/FutureGrantManager.java
+++ 
b/core/src/main/java/org/apache/gravitino/authorization/FutureGrantManager.java
@@ -20,6 +20,7 @@ package org.apache.gravitino.authorization;
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -37,7 +38,6 @@ import 
org.apache.gravitino.connector.authorization.AuthorizationPlugin;
 import org.apache.gravitino.meta.GroupEntity;
 import org.apache.gravitino.meta.RoleEntity;
 import org.apache.gravitino.meta.UserEntity;
-import org.glassfish.jersey.internal.guava.Sets;
 
 /**
  * FutureGrantManager is responsible for granting privileges to future object. 
When you grant a
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java 
b/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java
index 8b195894f..dc675fdce 100644
--- a/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java
+++ b/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java
@@ -27,15 +27,19 @@ import java.util.Map;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.EntityAlreadyExistsException;
 import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Namespace;
+import org.apache.gravitino.SupportsRelationOperations;
 import org.apache.gravitino.exceptions.NoSuchEntityException;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchRoleException;
 import org.apache.gravitino.exceptions.RoleAlreadyExistsException;
 import org.apache.gravitino.meta.AuditInfo;
 import org.apache.gravitino.meta.RoleEntity;
 import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.utils.MetadataObjectUtil;
 import org.apache.gravitino.utils.PrincipalUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -148,6 +152,35 @@ class RoleManager {
     }
   }
 
+  String[] listRoleNamesByObject(String metalake, MetadataObject object) {
+    try {
+      AuthorizationUtils.checkMetalakeExists(metalake);
+
+      return store.relationOperations()
+          .listEntitiesByRelation(
+              SupportsRelationOperations.Type.METADATA_OBJECT_ROLE_REL,
+              MetadataObjectUtil.toEntityIdent(metalake, object),
+              MetadataObjectUtil.toEntityType(object),
+              false /* allFields */)
+          .stream()
+          .map(entity -> ((RoleEntity) entity).name())
+          .toArray(String[]::new);
+
+    } catch (NoSuchEntityException nse) {
+      LOG.error("Metadata object {} (type {}) doesn't exist", 
object.fullName(), object.type());
+      throw new NoSuchMetadataObjectException(
+          "Metadata object %s (type %s) doesn't exist", object.fullName(), 
object.type());
+    } catch (IOException ioe) {
+      LOG.error(
+          "Listing roles under metalake {} by object full name {} and type {} 
failed due to storage issues",
+          metalake,
+          object.fullName(),
+          object.type(),
+          ioe);
+      throw new RuntimeException(ioe);
+    }
+  }
+
   private RoleEntity getRoleEntity(NameIdentifier identifier) {
     try {
       return store.get(identifier, Entity.EntityType.ROLE, RoleEntity.class);
diff --git 
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java 
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
index 7882e9c8a..65ed2c9da 100644
--- 
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
+++ 
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Map;
 import org.apache.gravitino.Entity;
 import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.MetadataObject;
 import org.apache.gravitino.authorization.AccessControlDispatcher;
 import org.apache.gravitino.authorization.AuthorizationUtils;
 import org.apache.gravitino.authorization.Group;
@@ -32,6 +33,7 @@ import org.apache.gravitino.authorization.SecurableObject;
 import org.apache.gravitino.authorization.User;
 import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchGroupException;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchRoleException;
 import org.apache.gravitino.exceptions.NoSuchUserException;
@@ -162,4 +164,10 @@ public class AccessControlHookDispatcher implements 
AccessControlDispatcher {
   public String[] listRoleNames(String metalake) throws 
NoSuchMetalakeException {
     return dispatcher.listRoleNames(metalake);
   }
+
+  @Override
+  public String[] listRoleNamesByObject(String metalake, MetadataObject object)
+      throws NoSuchMetalakeException, NoSuchMetadataObjectException {
+    return dispatcher.listRoleNamesByObject(metalake, object);
+  }
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index 2b9a6d0e4..42b079234 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -369,9 +369,7 @@ public class JDBCBackend implements RelationalBackend {
 
   @Override
   public <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
-      SupportsRelationOperations.Type relType,
-      NameIdentifier nameIdentifier,
-      Entity.EntityType identType) {
+      Type relType, NameIdentifier nameIdentifier, Entity.EntityType 
identType, boolean allFields) {
     switch (relType) {
       case OWNER_REL:
         List<E> list = Lists.newArrayList();
@@ -382,7 +380,7 @@ public class JDBCBackend implements RelationalBackend {
       case METADATA_OBJECT_ROLE_REL:
         return (List<E>)
             RoleMetaService.getInstance()
-                .listRolesByMetadataObjectIdentAndType(nameIdentifier, 
identType);
+                .listRolesByMetadataObjectIdentAndType(nameIdentifier, 
identType, allFields);
       case ROLE_GROUP_REL:
         if (identType == Entity.EntityType.ROLE) {
           return (List<E>) 
GroupMetaService.getInstance().listGroupsByRoleIdent(nameIdentifier);
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
index c95db1a07..a337e7a78 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
@@ -188,11 +188,9 @@ public class RelationalEntityStore
 
   @Override
   public <E extends Entity & HasIdentifier> List<E> listEntitiesByRelation(
-      SupportsRelationOperations.Type relType,
-      NameIdentifier nameIdentifier,
-      Entity.EntityType identType)
+      Type relType, NameIdentifier nameIdentifier, Entity.EntityType 
identType, boolean allFields)
       throws IOException {
-    return backend.listEntitiesByRelation(relType, nameIdentifier, identType);
+    return backend.listEntitiesByRelation(relType, nameIdentifier, identType, 
allFields);
   }
 
   @Override
diff --git 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
index 1e914f59a..915a14950 100644
--- 
a/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
+++ 
b/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java
@@ -57,21 +57,6 @@ public class RoleMetaService {
 
   private RoleMetaService() {}
 
-  private RolePO getRolePOByMetalakeIdAndName(Long metalakeId, String 
roleName) {
-    RolePO rolePO =
-        SessionUtils.getWithoutCommit(
-            RoleMetaMapper.class,
-            mapper -> mapper.selectRoleMetaByMetalakeIdAndName(metalakeId, 
roleName));
-
-    if (rolePO == null) {
-      throw new NoSuchEntityException(
-          NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
-          Entity.EntityType.ROLE.name().toLowerCase(),
-          roleName);
-    }
-    return rolePO;
-  }
-
   public Long getRoleIdByMetalakeIdAndName(Long metalakeId, String roleName) {
     Long roleId =
         SessionUtils.getWithoutCommit(
@@ -93,7 +78,7 @@ public class RoleMetaService {
   }
 
   public List<RoleEntity> listRolesByMetadataObjectIdentAndType(
-      NameIdentifier metadataObjectIdent, Entity.EntityType 
metadataObjectType) {
+      NameIdentifier metadataObjectIdent, Entity.EntityType 
metadataObjectType, boolean allFields) {
     String metalake = NameIdentifierUtil.getMetalake(metadataObjectIdent);
     long metalakeId = 
MetalakeMetaService.getInstance().getMetalakeIdByName(metalake);
     MetadataObject metadataObject =
@@ -109,35 +94,18 @@ public class RoleMetaService {
                     metadataObjectId, metadataObject.type().name()));
     return rolePOs.stream()
         .map(
-            po ->
-                POConverters.fromRolePO(
-                    po, listSecurableObjects(po), 
AuthorizationUtils.ofRoleNamespace(metalake)))
+            po -> {
+              if (allFields) {
+                return POConverters.fromRolePO(
+                    po, listSecurableObjects(po), 
AuthorizationUtils.ofRoleNamespace(metalake));
+              } else {
+                return POConverters.fromRolePO(
+                    po, Collections.emptyList(), 
AuthorizationUtils.ofRoleNamespace(metalake));
+              }
+            })
         .collect(Collectors.toList());
   }
 
-  private List<SecurableObject> listSecurableObjects(RolePO po) {
-    List<SecurableObjectPO> securableObjectPOs = 
listSecurableObjectsByRoleId(po.getRoleId());
-    List<SecurableObject> securableObjects = Lists.newArrayList();
-
-    for (SecurableObjectPO securableObjectPO : securableObjectPOs) {
-      String fullName =
-          MetadataObjectService.getMetadataObjectFullName(
-              securableObjectPO.getType(), 
securableObjectPO.getMetadataObjectId());
-      if (fullName != null) {
-        securableObjects.add(
-            POConverters.fromSecurableObjectPO(
-                fullName, securableObjectPO, 
getType(securableObjectPO.getType())));
-      } else {
-        LOG.info(
-            "The securable object {} {} may be deleted",
-            securableObjectPO.getMetadataObjectId(),
-            securableObjectPO.getType());
-      }
-    }
-
-    return securableObjects;
-  }
-
   public List<RolePO> listRolesByGroupId(Long groupId) {
     return SessionUtils.getWithoutCommit(
         RoleMetaMapper.class, mapper -> mapper.listRolesByGroupId(groupId));
@@ -234,7 +202,7 @@ public class RoleMetaService {
     return true;
   }
 
-  private List<SecurableObjectPO> listSecurableObjectsByRoleId(Long roleId) {
+  private static List<SecurableObjectPO> listSecurableObjectsByRoleId(Long 
roleId) {
     return SessionUtils.getWithoutCommit(
         SecurableObjectMapper.class, mapper -> 
mapper.listSecurableObjectsByRoleId(roleId));
   }
@@ -291,11 +259,49 @@ public class RoleMetaService {
         + securableObjectsCount[0];
   }
 
-  private MetadataObject.Type getType(String type) {
+  private static List<SecurableObject> listSecurableObjects(RolePO po) {
+    List<SecurableObjectPO> securableObjectPOs = 
listSecurableObjectsByRoleId(po.getRoleId());
+    List<SecurableObject> securableObjects = Lists.newArrayList();
+
+    for (SecurableObjectPO securableObjectPO : securableObjectPOs) {
+      String fullName =
+          MetadataObjectService.getMetadataObjectFullName(
+              securableObjectPO.getType(), 
securableObjectPO.getMetadataObjectId());
+      if (fullName != null) {
+        securableObjects.add(
+            POConverters.fromSecurableObjectPO(
+                fullName, securableObjectPO, 
getType(securableObjectPO.getType())));
+      } else {
+        LOG.warn(
+            "The securable object {} {} may be deleted",
+            securableObjectPO.getMetadataObjectId(),
+            securableObjectPO.getType());
+      }
+    }
+
+    return securableObjects;
+  }
+
+  private static RolePO getRolePOByMetalakeIdAndName(Long metalakeId, String 
roleName) {
+    RolePO rolePO =
+        SessionUtils.getWithoutCommit(
+            RoleMetaMapper.class,
+            mapper -> mapper.selectRoleMetaByMetalakeIdAndName(metalakeId, 
roleName));
+
+    if (rolePO == null) {
+      throw new NoSuchEntityException(
+          NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+          Entity.EntityType.ROLE.name().toLowerCase(),
+          roleName);
+    }
+    return rolePO;
+  }
+
+  private static MetadataObject.Type getType(String type) {
     return MetadataObject.Type.valueOf(type);
   }
 
-  private String getEntityType(SecurableObject securableObject) {
+  private static String getEntityType(SecurableObject securableObject) {
     return securableObject.type().name();
   }
 }
diff --git 
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
 
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
index 6dfaf54fe..b299c15ef 100644
--- 
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
+++ 
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
@@ -115,6 +115,7 @@ public class TestAccessControlManager {
   public static void setUp() throws Exception {
     File dbDir = new File(DB_DIR);
     dbDir.mkdirs();
+
     
Mockito.when(config.get(SERVICE_ADMINS)).thenReturn(Lists.newArrayList("admin1",
 "admin2"));
     Mockito.when(config.get(ENTITY_STORE)).thenReturn(RELATIONAL_ENTITY_STORE);
     
Mockito.when(config.get(ENTITY_RELATIONAL_STORE)).thenReturn(DEFAULT_ENTITY_RELATIONAL_STORE);
@@ -125,10 +126,12 @@ public class TestAccessControlManager {
     Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 
1000L);
     Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L);
     
Mockito.when(config.get(CATALOG_CACHE_EVICTION_INTERVAL_MS)).thenReturn(1000L);
+
     Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
     Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
     Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
     FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new 
LockManager(config), true);
+
     entityStore = EntityStoreFactory.createEntityStore(config);
     entityStore.initialize(config);
 
@@ -146,6 +149,7 @@ public class TestAccessControlManager {
                 
AuditInfo.builder().withCreator("test").withCreateTime(Instant.now()).build())
             .build();
     entityStore.put(catalogEntity, true);
+
     CatalogEntity anotherCatalogEntity =
         CatalogEntity.builder()
             .withId(4L)
@@ -421,6 +425,31 @@ public class TestAccessControlManager {
     String[] actualRoles = accessControlManager.listRoleNames("metalake_list");
     Arrays.sort(actualRoles);
     Assertions.assertArrayEquals(new String[] {"testList1", "testList2"}, 
actualRoles);
+
+    accessControlManager.deleteRole("metalake_list", "testList1");
+    accessControlManager.deleteRole("metalake_list", "testList2");
+  }
+
+  @Test
+  public void testListRolesByObject() {
+    Map<String, String> props = ImmutableMap.of("k1", "v1");
+    SecurableObject catalogObject =
+        SecurableObjects.ofCatalog("catalog", 
Lists.newArrayList(Privileges.UseCatalog.allow()));
+
+    accessControlManager.createRole(
+        "metalake_list", "testList1", props, 
Lists.newArrayList(catalogObject));
+
+    accessControlManager.createRole(
+        "metalake_list", "testList2", props, 
Lists.newArrayList(catalogObject));
+
+    // Test to list roles
+    String[] listedRoles =
+        accessControlManager.listRoleNamesByObject("metalake_list", 
catalogObject);
+    Arrays.sort(listedRoles);
+    Assertions.assertArrayEquals(new String[] {"testList1", "testList2"}, 
listedRoles);
+
+    accessControlManager.deleteRole("metalake_list", "testList1");
+    accessControlManager.deleteRole("metalake_list", "testList2");
   }
 
   private void testProperties(Map<String, String> expectedProps, Map<String, 
String> testProps) {
diff --git 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
index 4a781f018..1f818b112 100644
--- 
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
+++ 
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java
@@ -441,7 +441,7 @@ class TestRoleMetaService extends TestJDBCBackend {
 
     List<RoleEntity> roleEntities =
         roleMetaService.listRolesByMetadataObjectIdentAndType(
-            catalog.nameIdentifier(), catalog.type());
+            catalog.nameIdentifier(), catalog.type(), true);
     roleEntities.sort(Comparator.comparing(RoleEntity::name));
     Assertions.assertEquals(Lists.newArrayList(role1, role2), roleEntities);
   }
diff --git 
a/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectRoleOperations.java
 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectRoleOperations.java
new file mode 100644
index 000000000..ad27b22a3
--- /dev/null
+++ 
b/server/src/main/java/org/apache/gravitino/server/web/rest/MetadataObjectRoleOperations.java
@@ -0,0 +1,89 @@
+/*
+ * 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.web.rest;
+
+import com.codahale.metrics.annotation.ResponseMetered;
+import com.codahale.metrics.annotation.Timed;
+import java.util.Locale;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AccessControlDispatcher;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.lock.LockType;
+import org.apache.gravitino.lock.TreeLockUtils;
+import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.server.authorization.NameBindings;
+import org.apache.gravitino.server.web.Utils;
+import org.apache.gravitino.utils.MetadataObjectUtil;
+
[email protected]
+@Path("/metalakes/{metalake}/objects/{type}/{fullName}/roles")
+public class MetadataObjectRoleOperations {
+
+  private final AccessControlDispatcher accessControlDispatcher;
+
+  @Context private HttpServletRequest httpRequest;
+
+  public MetadataObjectRoleOperations() {
+    // Because accessControlManager may be null when Gravitino doesn't enable 
authorization,
+    // and Jersey injection doesn't support null value. So 
MedataObjectRoleOperations chooses to
+    // retrieve
+    // accessControlDispatcher from GravitinoEnv instead of injection here.
+    this.accessControlDispatcher = 
GravitinoEnv.getInstance().accessControlDispatcher();
+  }
+
+  @GET
+  @Produces("application/vnd.gravitino.v1+json")
+  @Timed(name = "list-role-by-object." + MetricNames.HTTP_PROCESS_DURATION, 
absolute = true)
+  @ResponseMetered(name = "list-role-by-object", absolute = true)
+  public Response listRoles(
+      @PathParam("metalake") String metalake,
+      @PathParam("type") String type,
+      @PathParam("fullName") String fullName) {
+    try {
+      MetadataObject object =
+          MetadataObjects.parse(
+              fullName, 
MetadataObject.Type.valueOf(type.toUpperCase(Locale.ROOT)));
+
+      NameIdentifier identifier = MetadataObjectUtil.toEntityIdent(metalake, 
object);
+      return Utils.doAs(
+          httpRequest,
+          () ->
+              TreeLockUtils.doWithTreeLock(
+                  identifier,
+                  LockType.READ,
+                  () -> {
+                    String[] names =
+                        
accessControlDispatcher.listRoleNamesByObject(metalake, object);
+                    return Utils.ok(new NameListResponse(names));
+                  }));
+    } catch (Exception e) {
+      return ExceptionHandlers.handleRoleException(OperationType.LIST, "", 
metalake, e);
+    }
+  }
+}
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectRoleOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectRoleOperations.java
new file mode 100644
index 000000000..19c545a08
--- /dev/null
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestMetadataObjectRoleOperations.java
@@ -0,0 +1,146 @@
+/*
+ * 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.web.rest;
+
+import static org.apache.gravitino.Configs.TREE_LOCK_CLEAN_INTERVAL;
+import static org.apache.gravitino.Configs.TREE_LOCK_MAX_NODE_IN_MEMORY;
+import static org.apache.gravitino.Configs.TREE_LOCK_MIN_NODE_IN_MEMORY;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.reflect.FieldUtils;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.authorization.AccessControlManager;
+import org.apache.gravitino.dto.responses.ErrorConstants;
+import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.lock.LockManager;
+import org.apache.gravitino.rest.RESTUtils;
+import org.glassfish.hk2.utilities.binding.AbstractBinder;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.glassfish.jersey.test.TestProperties;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+public class TestMetadataObjectRoleOperations extends JerseyTest {
+
+  private static final AccessControlManager manager = 
mock(AccessControlManager.class);
+
+  private static class MockServletRequestFactory extends 
ServletRequestFactoryBase {
+    @Override
+    public HttpServletRequest get() {
+      HttpServletRequest request = mock(HttpServletRequest.class);
+      when(request.getRemoteUser()).thenReturn(null);
+      return request;
+    }
+  }
+
+  @BeforeAll
+  public static void setup() throws IllegalAccessException {
+    Config config = mock(Config.class);
+    Mockito.doReturn(100000L).when(config).get(TREE_LOCK_MAX_NODE_IN_MEMORY);
+    Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY);
+    Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new 
LockManager(config), true);
+    FieldUtils.writeField(GravitinoEnv.getInstance(), 
"accessControlDispatcher", manager, true);
+  }
+
+  @Override
+  protected Application configure() {
+    try {
+      forceSet(
+          TestProperties.CONTAINER_PORT, 
String.valueOf(RESTUtils.findAvailablePort(2000, 3000)));
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+
+    ResourceConfig resourceConfig = new ResourceConfig();
+    resourceConfig.register(MetadataObjectRoleOperations.class);
+    resourceConfig.register(
+        new AbstractBinder() {
+          @Override
+          protected void configure() {
+            
bindFactory(MockServletRequestFactory.class).to(HttpServletRequest.class);
+          }
+        });
+
+    return resourceConfig;
+  }
+
+  @Test
+  public void testListRoleNames() {
+    when(manager.listRoleNamesByObject(any(), any())).thenReturn(new String[] 
{"role"});
+
+    Response resp =
+        target("/metalakes/metalake1/objects/metalake/metalake1/roles/")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+    Assertions.assertEquals(Response.Status.OK.getStatusCode(), 
resp.getStatus());
+
+    NameListResponse listResponse = resp.readEntity(NameListResponse.class);
+    Assertions.assertEquals(0, listResponse.getCode());
+
+    Assertions.assertEquals(1, listResponse.getNames().length);
+    Assertions.assertEquals("role", listResponse.getNames()[0]);
+
+    // Test to throw NoSuchMetalakeException
+    doThrow(new NoSuchMetalakeException("mock error"))
+        .when(manager)
+        .listRoleNamesByObject(any(), any());
+    Response resp1 =
+        target("/metalakes/metalake1/objects/metalake/metalake1/roles/")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), 
resp1.getStatus());
+
+    ErrorResponse errorResponse = resp1.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, 
errorResponse.getCode());
+    Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(), 
errorResponse.getType());
+
+    // Test to throw internal RuntimeException
+    doThrow(new RuntimeException("mock 
error")).when(manager).listRoleNamesByObject(any(), any());
+    Response resp3 =
+        target("/metalakes/metalake1/objects/metalake/metalake1/roles")
+            .request(MediaType.APPLICATION_JSON_TYPE)
+            .accept("application/vnd.gravitino.v1+json")
+            .get();
+
+    Assertions.assertEquals(
+        Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), 
resp3.getStatus());
+
+    ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+    Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
+    Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse2.getType());
+  }
+}
diff --git 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java
 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java
index eb365d1ac..a2f0c4847 100644
--- 
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java
+++ 
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java
@@ -334,23 +334,6 @@ public class TestRoleOperations extends JerseyTest {
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse2.getType());
   }
 
-  private Role buildRole(String role) {
-    SecurableObject catalog =
-        SecurableObjects.ofCatalog("catalog", 
Lists.newArrayList(Privileges.UseCatalog.allow()));
-    SecurableObject anotherSecurableObject =
-        SecurableObjects.ofCatalog(
-            "another_catalog", 
Lists.newArrayList(Privileges.CreateSchema.deny()));
-
-    return RoleEntity.builder()
-        .withId(1L)
-        .withName(role)
-        .withProperties(Collections.emptyMap())
-        .withSecurableObjects(Lists.newArrayList(catalog, 
anotherSecurableObject))
-        .withAuditInfo(
-            
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build())
-        .build();
-  }
-
   @Test
   public void testDeleteRole() {
     when(manager.deleteRole(any(), any())).thenReturn(true);
@@ -502,4 +485,21 @@ public class TestRoleOperations extends JerseyTest {
     Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, 
errorResponse2.getCode());
     Assertions.assertEquals(RuntimeException.class.getSimpleName(), 
errorResponse2.getType());
   }
+
+  private Role buildRole(String role) {
+    SecurableObject catalog =
+        SecurableObjects.ofCatalog("catalog", 
Lists.newArrayList(Privileges.UseCatalog.allow()));
+    SecurableObject anotherSecurableObject =
+        SecurableObjects.ofCatalog(
+            "another_catalog", 
Lists.newArrayList(Privileges.CreateSchema.deny()));
+
+    return RoleEntity.builder()
+        .withId(1L)
+        .withName(role)
+        .withProperties(Collections.emptyMap())
+        .withSecurableObjects(Lists.newArrayList(catalog, 
anotherSecurableObject))
+        .withAuditInfo(
+            
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build())
+        .build();
+  }
 }

Reply via email to