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 <[email protected]>
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.