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.