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 818362b0ea [6346] feat(authz): Add privilege support for model (#6820)
818362b0ea is described below

commit 818362b0ea2c4ec8f67bc2e2e431aa7295e74313
Author: roryqi <h...@datastrato.com>
AuthorDate: Tue Apr 8 00:43:08 2025 +0800

    [6346] feat(authz): Add privilege support for model (#6820)
    
    ### What changes were proposed in this pull request?
    
     Add privilege support for model
    
    ### Why are the changes needed?
    
    Fix: #6346
    
    ### Does this PR introduce _any_ user-facing change?
    
    Added the doucments
    
    ### How was this patch tested?
    
    UT.
---
 .../apache/gravitino/authorization/Privilege.java  |   8 +-
 .../apache/gravitino/authorization/Privileges.java | 103 +++++++++++++++++++++
 .../gravitino/authorization/SecurableObjects.java  |  16 ++++
 .../authorization/TestSecurableObjects.java        |  35 +++++++
 .../test/authorization/AccessControlIT.java        |  88 ++++++++++++++++++
 .../authorization/AuthorizationUtils.java          |  10 ++
 docs/security/access-control.md                    |   8 ++
 7 files changed, 267 insertions(+), 1 deletion(-)

diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java 
b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
index 866cf7d23a..5515e45fbd 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
@@ -84,7 +84,13 @@ public interface Privilege {
     /** The privilege to create a role */
     CREATE_ROLE(0L, 1L << 16),
     /** The privilege to grant or revoke a role for the user or the group. */
-    MANAGE_GRANTS(0L, 1L << 17);
+    MANAGE_GRANTS(0L, 1L << 17),
+    /** The privilege to create a model */
+    CREATE_MODEL(0L, 1L << 18),
+    /** The privilege to create a model version */
+    CREATE_MODEL_VERSION(0L, 1L << 19),
+    /** The privilege to view the metadata of the model and download all the 
model versions */
+    USE_MODEL(0L, 1L << 20);
 
     private final long highBits;
     private final long lowBits;
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java 
b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
index b0f9e8fcc4..cf3bdb0692 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
@@ -32,6 +32,14 @@ public class Privileges {
           MetadataObject.Type.CATALOG,
           MetadataObject.Type.SCHEMA,
           MetadataObject.Type.TABLE);
+
+  private static final Set<MetadataObject.Type> MODEL_SUPPORTED_TYPES =
+      Sets.immutableEnumSet(
+          MetadataObject.Type.METALAKE,
+          MetadataObject.Type.CATALOG,
+          MetadataObject.Type.SCHEMA,
+          MetadataObject.Type.MODEL);
+
   private static final Set<MetadataObject.Type> TOPIC_SUPPORTED_TYPES =
       Sets.immutableEnumSet(
           MetadataObject.Type.METALAKE,
@@ -118,6 +126,14 @@ public class Privileges {
       case MANAGE_GRANTS:
         return ManageGrants.allow();
 
+        //  Model
+      case CREATE_MODEL:
+        return CreateModel.allow();
+      case CREATE_MODEL_VERSION:
+        return CreateModelVersion.allow();
+      case USE_MODEL:
+        return UseModel.allow();
+
       default:
         throw new IllegalArgumentException("Doesn't support the privilege: " + 
name);
     }
@@ -192,6 +208,14 @@ public class Privileges {
       case MANAGE_GRANTS:
         return ManageGrants.deny();
 
+        // Model
+      case CREATE_MODEL:
+        return CreateModel.deny();
+      case CREATE_MODEL_VERSION:
+        return CreateModelVersion.deny();
+      case USE_MODEL:
+        return UseModel.deny();
+
       default:
         throw new IllegalArgumentException("Doesn't support the privilege: " + 
name);
     }
@@ -703,4 +727,83 @@ public class Privileges {
       return type == MetadataObject.Type.METALAKE;
     }
   }
+
+  /** The privilege to create a model */
+  public static class CreateModel extends GenericPrivilege<CreateModel> {
+    private static final CreateModel ALLOW_INSTANCE =
+        new CreateModel(Condition.ALLOW, Name.CREATE_MODEL);
+    private static final CreateModel DENY_INSTANCE =
+        new CreateModel(Condition.DENY, Name.CREATE_MODEL);
+
+    private CreateModel(Condition condition, Name name) {
+      super(condition, name);
+    }
+
+    /** @return The instance with allow condition of the privilege. */
+    public static CreateModel allow() {
+      return ALLOW_INSTANCE;
+    }
+
+    /** @return The instance with deny condition of the privilege. */
+    public static CreateModel deny() {
+      return DENY_INSTANCE;
+    }
+
+    @Override
+    public boolean canBindTo(MetadataObject.Type type) {
+      return SCHEMA_SUPPORTED_TYPES.contains(type);
+    }
+  }
+
+  /** The privilege to view the metadata of the model and download all the 
model versions */
+  public static class UseModel extends GenericPrivilege<UseModel> {
+    private static final UseModel ALLOW_INSTANCE = new 
UseModel(Condition.ALLOW, Name.USE_MODEL);
+    private static final UseModel DENY_INSTANCE = new UseModel(Condition.DENY, 
Name.USE_MODEL);
+
+    private UseModel(Condition condition, Name name) {
+      super(condition, name);
+    }
+
+    /** @return The instance with allow condition of the privilege. */
+    public static UseModel allow() {
+      return ALLOW_INSTANCE;
+    }
+
+    /** @return The instance with deny condition of the privilege. */
+    public static UseModel deny() {
+      return DENY_INSTANCE;
+    }
+
+    @Override
+    public boolean canBindTo(MetadataObject.Type type) {
+      return MODEL_SUPPORTED_TYPES.contains(type);
+    }
+  }
+
+  /** The privilege to create a model version */
+  public static class CreateModelVersion extends 
GenericPrivilege<CreateModelVersion> {
+    private static final CreateModelVersion ALLOW_INSTANCE =
+        new CreateModelVersion(Condition.ALLOW, Name.CREATE_MODEL_VERSION);
+    private static final CreateModelVersion DENY_INSTANCE =
+        new CreateModelVersion(Condition.DENY, Name.CREATE_MODEL_VERSION);
+
+    private CreateModelVersion(Condition condition, Name name) {
+      super(condition, name);
+    }
+
+    /** @return The instance with allow condition of the privilege. */
+    public static CreateModelVersion allow() {
+      return ALLOW_INSTANCE;
+    }
+
+    /** @return The instance with deny condition of the privilege. */
+    public static CreateModelVersion deny() {
+      return DENY_INSTANCE;
+    }
+
+    @Override
+    public boolean canBindTo(MetadataObject.Type type) {
+      return MODEL_SUPPORTED_TYPES.contains(type);
+    }
+  }
 }
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java 
b/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
index 5aa2fd8d2f..e63e3d0982 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
@@ -122,6 +122,22 @@ public class SecurableObjects {
     return of(MetadataObject.Type.FILESET, names, privileges);
   }
 
+  /**
+   * Create the model {@link SecurableObject} with the given securable schema 
object, model name and
+   * privileges.
+   *
+   * @param schema The schema securable object
+   * @param model The model name
+   * @param privileges The privileges of the fileset
+   * @return The created model {@link SecurableObject}
+   */
+  public static SecurableObject ofModel(
+      SecurableObject schema, String model, List<Privilege> privileges) {
+    List<String> names = 
Lists.newArrayList(DOT_SPLITTER.splitToList(schema.fullName()));
+    names.add(model);
+    return of(MetadataObject.Type.MODEL, names, privileges);
+  }
+
   private static class SecurableObjectImpl extends MetadataObjectImpl 
implements SecurableObject {
 
     private List<Privilege> privileges;
diff --git 
a/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
 
b/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
index f3066666d9..4283dd53cf 100644
--- 
a/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
+++ 
b/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
@@ -177,6 +177,9 @@ public class TestSecurableObjects {
     Privilege manageUsers = Privileges.ManageUsers.allow();
     Privilege manageGroups = Privileges.ManageGroups.allow();
     Privilege manageGrants = Privileges.ManageGrants.allow();
+    Privilege createModel = Privileges.CreateModel.allow();
+    Privilege createModelVersion = Privileges.CreateModelVersion.allow();
+    Privilege useModel = Privileges.UseModel.allow();
 
     // Test create catalog
     
Assertions.assertTrue(createCatalog.canBindTo(MetadataObject.Type.METALAKE));
@@ -347,5 +350,37 @@ public class TestSecurableObjects {
     
Assertions.assertFalse(manageGrants.canBindTo(MetadataObject.Type.FILESET));
     Assertions.assertFalse(manageGrants.canBindTo(MetadataObject.Type.ROLE));
     Assertions.assertFalse(manageGrants.canBindTo(MetadataObject.Type.COLUMN));
+
+    // Test create model
+    Assertions.assertTrue(createModel.canBindTo(MetadataObject.Type.METALAKE));
+    Assertions.assertTrue(createModel.canBindTo(MetadataObject.Type.CATALOG));
+    Assertions.assertTrue(createModel.canBindTo(MetadataObject.Type.SCHEMA));
+    Assertions.assertFalse(createModel.canBindTo(MetadataObject.Type.TABLE));
+    Assertions.assertFalse(createModel.canBindTo(MetadataObject.Type.TOPIC));
+    Assertions.assertFalse(createModel.canBindTo(MetadataObject.Type.FILESET));
+    Assertions.assertFalse(createModel.canBindTo(MetadataObject.Type.ROLE));
+    Assertions.assertFalse(createModel.canBindTo(MetadataObject.Type.COLUMN));
+    Assertions.assertFalse(createModel.canBindTo(MetadataObject.Type.MODEL));
+    // Test create model version
+    
Assertions.assertTrue(createModelVersion.canBindTo(MetadataObject.Type.METALAKE));
+    
Assertions.assertTrue(createModelVersion.canBindTo(MetadataObject.Type.CATALOG));
+    
Assertions.assertTrue(createModelVersion.canBindTo(MetadataObject.Type.SCHEMA));
+    
Assertions.assertFalse(createModelVersion.canBindTo(MetadataObject.Type.TABLE));
+    
Assertions.assertFalse(createModelVersion.canBindTo(MetadataObject.Type.TOPIC));
+    
Assertions.assertFalse(createModelVersion.canBindTo(MetadataObject.Type.FILESET));
+    
Assertions.assertFalse(createModelVersion.canBindTo(MetadataObject.Type.ROLE));
+    
Assertions.assertFalse(createModelVersion.canBindTo(MetadataObject.Type.COLUMN));
+    
Assertions.assertTrue(createModelVersion.canBindTo(MetadataObject.Type.MODEL));
+
+    // Test use model
+    Assertions.assertTrue(useModel.canBindTo(MetadataObject.Type.METALAKE));
+    Assertions.assertTrue(useModel.canBindTo(MetadataObject.Type.CATALOG));
+    Assertions.assertTrue(useModel.canBindTo(MetadataObject.Type.SCHEMA));
+    Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.TABLE));
+    Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.TOPIC));
+    Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.FILESET));
+    Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.ROLE));
+    Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.COLUMN));
+    Assertions.assertTrue(useModel.canBindTo(MetadataObject.Type.MODEL));
   }
 }
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 1b24bb9083..75262bb3c3 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
@@ -74,11 +74,20 @@ public class AccessControlIT extends BaseIT {
     Catalog filesetCatalog =
         metalake.createCatalog(
             "fileset_catalog", Catalog.Type.FILESET, "hadoop", "comment", 
Collections.emptyMap());
+
+    Catalog modelCatalog =
+        metalake.createCatalog(
+            "model_catalog", Catalog.Type.MODEL, "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());
+
+    NameIdentifier modelIdent = NameIdentifier.of("model_schema", "model");
+    modelCatalog.asSchemas().createSchema("model_schema", "comment", 
Collections.emptyMap());
+    modelCatalog.asModelCatalog().registerModel(modelIdent, "comment", 
Collections.emptyMap());
   }
 
   @Test
@@ -218,6 +227,75 @@ public class AccessControlIT extends BaseIT {
     Assertions.assertEquals(createdPrivilege.name(), 
Privilege.Name.CREATE_CATALOG);
     Assertions.assertEquals(createdPrivilege.condition(), 
Privilege.Condition.ALLOW);
 
+    // Test a metalake object with model privileges
+    SecurableObject metalakeObjectWithModelPrivs =
+        SecurableObjects.ofMetalake(
+            metalakeName,
+            Lists.newArrayList(
+                Privileges.CreateModel.allow(),
+                Privileges.CreateModelVersion.allow(),
+                Privileges.UseModel.allow()));
+
+    role =
+        metalake.createRole(
+            "model_name", properties, 
Lists.newArrayList(metalakeObjectWithModelPrivs));
+
+    Assertions.assertEquals("model_name", role.name());
+    Assertions.assertEquals(properties, role.properties());
+    metalake.deleteRole("model_name");
+
+    SecurableObject catalogObjectWithModelPrivs =
+        SecurableObjects.ofCatalog(
+            "model_catalog",
+            Lists.newArrayList(
+                Privileges.CreateModel.allow(),
+                Privileges.CreateModelVersion.allow(),
+                Privileges.UseModel.allow()));
+    role =
+        metalake.createRole(
+            "model_name", properties, 
Lists.newArrayList(catalogObjectWithModelPrivs));
+    Assertions.assertEquals("model_name", role.name());
+    Assertions.assertEquals(properties, role.properties());
+    metalake.deleteRole("model_name");
+
+    SecurableObject schemaObjectWithModelPrivs =
+        SecurableObjects.ofSchema(
+            catalogObjectWithModelPrivs,
+            "model_schema",
+            Lists.newArrayList(
+                Privileges.CreateModel.allow(),
+                Privileges.CreateModelVersion.allow(),
+                Privileges.UseModel.allow()));
+    role =
+        metalake.createRole(
+            "model_name", properties, 
Lists.newArrayList(schemaObjectWithModelPrivs));
+    Assertions.assertEquals("model_name", role.name());
+    Assertions.assertEquals(properties, role.properties());
+    metalake.deleteRole("model_name");
+
+    SecurableObject modelObjectWithCorrectPriv =
+        SecurableObjects.ofModel(
+            schemaObjectWithModelPrivs,
+            "model",
+            Lists.newArrayList(Privileges.CreateModelVersion.allow(), 
Privileges.UseModel.allow()));
+    role =
+        metalake.createRole(
+            "model_name", properties, 
Lists.newArrayList(modelObjectWithCorrectPriv));
+    Assertions.assertEquals("model_name", role.name());
+    Assertions.assertEquals(properties, role.properties());
+    metalake.deleteRole("model_name");
+
+    SecurableObject modelObjectWithWrongPriv =
+        SecurableObjects.ofModel(
+            schemaObjectWithModelPrivs,
+            "model",
+            Lists.newArrayList(Privileges.CreateModel.allow()));
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            metalake.createRole(
+                "model_name", properties, 
Lists.newArrayList(modelObjectWithWrongPriv)));
+
     // Test a not-existed metadata object
     SecurableObject catalogObject =
         SecurableObjects.ofCatalog(
@@ -257,6 +335,16 @@ public class AccessControlIT extends BaseIT {
         () ->
             metalake.createRole("not-existed", properties, 
Lists.newArrayList(wrongCatalogObject)));
 
+    // Create a role with wrong model privilege
+    SecurableObject wrongCatalogObject2 =
+        SecurableObjects.ofCatalog(
+            "fileset_catalog", 
Lists.newArrayList(Privileges.CreateModel.allow()));
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () ->
+            metalake.createRole(
+                "not-existed", properties, 
Lists.newArrayList(wrongCatalogObject2)));
+
     // Create a role with duplicated privilege
     SecurableObject duplicatedCatalogObject =
         SecurableObjects.ofCatalog(
diff --git 
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java 
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
index 0400666bb7..03f1ab5845 100644
--- 
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++ 
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -77,6 +77,12 @@ public class AuthorizationUtils {
       Sets.immutableEnumSet(
           Privilege.Name.CREATE_TOPIC, Privilege.Name.PRODUCE_TOPIC, 
Privilege.Name.CONSUME_TOPIC);
 
+  private static final Set<Privilege.Name> MODEL_PRIVILEGES =
+      Sets.immutableEnumSet(
+          Privilege.Name.CREATE_MODEL,
+          Privilege.Name.USE_MODEL,
+          Privilege.Name.CREATE_MODEL_VERSION);
+
   private AuthorizationUtils() {}
 
   public static void checkCurrentUser(String metalake, String user) {
@@ -245,6 +251,10 @@ public class AuthorizationUtils {
         if (TOPIC_PRIVILEGES.contains(privilege.name())) {
           checkCatalogType(catalogIdent, Catalog.Type.MESSAGING, privilege);
         }
+
+        if (MODEL_PRIVILEGES.contains(privilege.name())) {
+          checkCatalogType(catalogIdent, Catalog.Type.MODEL, privilege);
+        }
       } catch (NoSuchCatalogException ne) {
         throw new NoSuchMetadataObjectException(
             "Securable object %s doesn't exist", object.fullName());
diff --git a/docs/security/access-control.md b/docs/security/access-control.md
index d5c676d92e..f2319222f8 100644
--- a/docs/security/access-control.md
+++ b/docs/security/access-control.md
@@ -224,6 +224,14 @@ and `USE_SCHEMA` privileges on its parent schema.
 | WRITE_FILESET  | Metalake, Catalog, Schema, Fileset | Write a fileset 
(including alter a fileset) |
 | READ_FILESET   | Metalake, Catalog, Schema, Fileset | read a fileset         
                     |
 
+### Model privileges
+
+| Name                 | Supports Securable Object        | Operation          
                                                |
+|----------------------|----------------------------------|--------------------------------------------------------------------|
+| CREATE_MODEL         | Metalake, Catalog, Schema        | Create a model     
                                                |
+| CREATE_MODEL_VERSION | Metalake, Catalog, Schema, Model | Create a model 
version                                             |
+| USE_MODEL            | Metalake, Catalog, Schema, Model | View the metadata 
of the model and download all the model versions |
+
 ## Inheritance Model
 
 Securable objects in Gravitino are hierarchical and privileges are inherited 
downward.

Reply via email to