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
commit f4ac64bcb35d4cd7c38bfd99e9cc37aeca3a5578 Author: [email protected] <[email protected]> AuthorDate: Tue Jul 15 15:31:23 2025 +0800 [#6762] feat(authz): Support Metadata Authorization --- .../java/org/apache/gravitino/GravitinoEnv.java | 20 ++ .../gravitino/authorization/OwnerManager.java | 2 +- gradle/libs.versions.toml | 9 +- server-common/build.gradle.kts | 5 +- .../server/authorization/MetadataIdConverter.java | 5 +- .../apache/gravitino/server/GravitinoServer.java | 7 +- .../server/web/rest/FilesetOperations.java | 102 +++++++--- .../gravitino/server/web/rest/ModelOperations.java | 215 ++++++++++++++++----- 8 files changed, 291 insertions(+), 74 deletions(-) diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java index e8b9f06ed3..ccb75f1764 100644 --- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java +++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java @@ -23,6 +23,7 @@ import org.apache.gravitino.audit.AuditLogManager; import org.apache.gravitino.authorization.AccessControlDispatcher; import org.apache.gravitino.authorization.AccessControlManager; import org.apache.gravitino.authorization.FutureGrantManager; +import org.apache.gravitino.authorization.GravitinoAuthorizer; import org.apache.gravitino.authorization.OwnerDispatcher; import org.apache.gravitino.authorization.OwnerEventManager; import org.apache.gravitino.authorization.OwnerManager; @@ -136,6 +137,7 @@ public class GravitinoEnv { private EventBus eventBus; private OwnerDispatcher ownerDispatcher; private FutureGrantManager futureGrantManager; + private GravitinoAuthorizer gravitinoAuthorizer; protected GravitinoEnv() {} @@ -366,6 +368,24 @@ public class GravitinoEnv { return eventListenerManager; } + /** + * Set GravitinoAuthorizer to GravitinoEnv + * + * @param gravitinoAuthorizer the GravitinoAuthorizer instance + */ + public void setGravitinoAuthorizer(GravitinoAuthorizer gravitinoAuthorizer) { + this.gravitinoAuthorizer = gravitinoAuthorizer; + } + + /** + * Get The GravitinoAuthorizer + * + * @return the GravitinoAuthorizer instance + */ + public GravitinoAuthorizer gravitinoAuthorizer() { + return gravitinoAuthorizer; + } + public void start() { metricsSystem.start(); eventListenerManager.start(); diff --git a/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java b/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java index 8e22b0e433..c6e1c0315f 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java +++ b/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java @@ -132,7 +132,6 @@ public class OwnerManager implements OwnerDispatcher { } } - @Override private void notifyOwnerChange(Owner oldOwner, String metalake, MetadataObject metadataObject) { GravitinoAuthorizer gravitinoAuthorizer = GravitinoEnv.getInstance().gravitinoAuthorizer(); if (gravitinoAuthorizer != null) { @@ -160,6 +159,7 @@ public class OwnerManager implements OwnerDispatcher { } } + @Override public Optional<Owner> getOwner(String metalake, MetadataObject metadataObject) { NameIdentifier ident = MetadataObjectUtil.toEntityIdent(metalake, metadataObject); OwnerImpl owner = new OwnerImpl(); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8330ca8759..4745cf87b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -119,10 +119,12 @@ hudi = "0.15.0" google-auth = "1.28.0" aliyun-credentials = "0.3.12" openlineage = "1.29.0" -concurrent-trees = "2.6.0" jcstress = "0.8.15" jmh-plugin = "0.7.3" jmh = "1.37" +jcasbin = "1.81.0" +ognl = "3.4.7" +concurrent-trees = "2.6.0" [libraries] aws-iam = { group = "software.amazon.awssdk", name = "iam", version.ref = "awssdk" } @@ -284,9 +286,10 @@ google-auth-credentials = { group = "com.google.auth", name = "google-auth-libra aliyun-credentials-sdk = { group='com.aliyun', name='credentials-java', version.ref='aliyun-credentials' } flinkjdbc = {group='org.apache.flink',name='flink-connector-jdbc', version.ref='flinkjdbc'} -openlineage-java= { group = "io.openlineage", name = "openlineage-java", version.ref = "openlineage" } - concurrent-trees = { group = "com.googlecode.concurrent-trees", name = "concurrent-trees", version.ref = "concurrent-trees" } +jcasbin = { group='org.casbin', name='jcasbin', version.ref="jcasbin" } +openlineage-java= { group = "io.openlineage", name = "openlineage-java", version.ref = "openlineage" } +ognl = { group='ognl', name='ognl', version.ref="ognl" } [bundles] log4j = ["slf4j-api", "log4j-slf4j2-impl", "log4j-api", "log4j-core", "log4j-12-api", "log4j-layout-template-json"] diff --git a/server-common/build.gradle.kts b/server-common/build.gradle.kts index f8d27a0446..943ff3758e 100644 --- a/server-common/build.gradle.kts +++ b/server-common/build.gradle.kts @@ -43,8 +43,11 @@ dependencies { implementation(libs.jackson.datatype.jdk8) implementation(libs.jackson.datatype.jsr310) implementation(libs.jackson.databind) - implementation(libs.prometheus.servlet) + implementation(libs.jcasbin) { + exclude(group = "com.fasterxml.jackson.core", module = "jackson-databind") + } implementation(libs.ognl) + implementation(libs.prometheus.servlet) testImplementation(libs.commons.io) testImplementation(libs.junit.jupiter.api) diff --git a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataIdConverter.java b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataIdConverter.java index fc1155d926..e7deaa15b0 100644 --- a/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataIdConverter.java +++ b/server-common/src/main/java/org/apache/gravitino/server/authorization/MetadataIdConverter.java @@ -97,9 +97,8 @@ public class MetadataIdConverter { * @param metadataObject The metadata object to convert. * @param metalake The metalake name. * @return The metadata id. - * @throws IOException if an error occurs while loading the entity. */ - public static Long getID(MetadataObject metadataObject, String metalake) throws IOException { + public static Long getID(MetadataObject metadataObject, String metalake) { Preconditions.checkArgument(metadataObject != null, "Metadata object cannot be null"); EntityStore entityStore = GravitinoEnv.getInstance().entityStore(); CatalogManager catalogManager = GravitinoEnv.getInstance().catalogManager(); @@ -119,7 +118,7 @@ public class MetadataIdConverter { try { entity = entityStore.get(normalizedIdent, entityType, getEntityClass(entityType)); } catch (IOException e) { - throw new IOException( + throw new RuntimeException( "failed to load entity from entity store: " + metadataObject.fullName(), e); } diff --git a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java index 582e0d105a..4262446fe6 100644 --- a/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java +++ b/server/src/main/java/org/apache/gravitino/server/GravitinoServer.java @@ -19,6 +19,7 @@ package org.apache.gravitino.server; import java.io.File; +import java.io.IOException; import java.util.HashSet; import java.util.Properties; import javax.inject.Singleton; @@ -40,6 +41,7 @@ import org.apache.gravitino.metalake.MetalakeDispatcher; import org.apache.gravitino.metrics.MetricsSystem; import org.apache.gravitino.metrics.source.MetricsSource; import org.apache.gravitino.server.authentication.ServerAuthenticator; +import org.apache.gravitino.server.authorization.GravitinoAuthorizerProvider; import org.apache.gravitino.server.web.ConfigServlet; import org.apache.gravitino.server.web.HttpServerMetricsSource; import org.apache.gravitino.server.web.JettyServer; @@ -98,6 +100,8 @@ public class GravitinoServer extends ResourceConfig { ServerAuthenticator.getInstance().initialize(serverConfig); + GravitinoAuthorizerProvider.getInstance().initialize(serverConfig); + lineageService.initialize( new LineageConfig(serverConfig.getConfigsWithPrefix(LineageConfig.LINEAGE_CONFIG_PREFIX))); @@ -177,7 +181,8 @@ public class GravitinoServer extends ResourceConfig { server.join(); } - public void stop() { + public void stop() throws IOException { + GravitinoAuthorizerProvider.getInstance().close(); server.stop(); gravitinoEnv.shutdown(); if (lineageService != null) { diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java index 2d4eb8ead7..94a4b03bde 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/FilesetOperations.java @@ -45,6 +45,8 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.apache.gravitino.Entity; +import org.apache.gravitino.MetadataObject; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; import org.apache.gravitino.audit.CallerContext; @@ -63,6 +65,9 @@ import org.apache.gravitino.file.Fileset; import org.apache.gravitino.file.FilesetChange; import org.apache.gravitino.metrics.MetricNames; import org.apache.gravitino.rest.RESTUtils; +import org.apache.gravitino.server.authorization.MetadataFilterHelper; +import org.apache.gravitino.server.authorization.annotations.AuthorizationExpression; +import org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata; import org.apache.gravitino.server.web.Utils; import org.apache.gravitino.utils.NameIdentifierUtil; import org.apache.gravitino.utils.NamespaceUtil; @@ -76,6 +81,11 @@ public class FilesetOperations { private final FilesetDispatcher dispatcher; + private static final String loadFilesetAuthorizationExpression = + "ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && (FILESET::OWNER || ANY_READ_FILESET || ANY_WRITE_FILESET)"; + @Context private HttpServletRequest httpRequest; @Inject @@ -88,9 +98,13 @@ public class FilesetOperations { @Timed(name = "list-fileset." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "list-fileset", absolute = true) public Response listFilesets( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema) { + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) + String schema) { + try { LOG.info("Received list filesets request for schema: {}.{}.{}", metalake, catalog, schema); return Utils.doAs( @@ -98,6 +112,12 @@ public class FilesetOperations { () -> { Namespace filesetNS = NamespaceUtil.ofFileset(metalake, catalog, schema); NameIdentifier[] idents = dispatcher.listFilesets(filesetNS); + idents = + MetadataFilterHelper.filterByExpression( + metalake, + loadFilesetAuthorizationExpression, + Entity.EntityType.FILESET, + idents); Response response = Utils.ok(new EntityListResponse(idents)); LOG.info( "List {} filesets under schema: {}.{}.{}", @@ -117,10 +137,18 @@ public class FilesetOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "create-fileset." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "create-fileset", absolute = true) + @AuthorizationExpression( + expression = + "ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && SCHEMA::CREATE_FILESET", + accessMetadataType = MetadataObject.Type.FILESET) public Response createFileset( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, FilesetCreateRequest request) { LOG.info( "Received create fileset request: {}.{}.{}.{}", @@ -168,11 +196,17 @@ public class FilesetOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "load-fileset." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "load-fileset", absolute = true) + @AuthorizationExpression( + expression = loadFilesetAuthorizationExpression, + accessMetadataType = MetadataObject.Type.FILESET) public Response loadFileset( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("fileset") String fileset) { + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("fileset") @AuthorizationMetadata(type = MetadataObject.Type.FILESET) + String fileset) { LOG.info("Received load fileset request: {}.{}.{}.{}", metalake, catalog, schema, fileset); try { return Utils.doAs( @@ -244,11 +278,20 @@ public class FilesetOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "alter-fileset." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "alter-fileset", absolute = true) + @AuthorizationExpression( + expression = + "ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && (FILESET::OWNER || ANY_WRITE_FILESET)", + accessMetadataType = MetadataObject.Type.FILESET) public Response alterFileset( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("fileset") String fileset, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("fileset") @AuthorizationMetadata(type = MetadataObject.Type.FILESET) + String fileset, FilesetUpdatesRequest request) { LOG.info("Received alter fileset request: {}.{}.{}.{}", metalake, catalog, schema, fileset); try { @@ -277,11 +320,20 @@ public class FilesetOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "drop-fileset." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "drop-fileset", absolute = true) + @AuthorizationExpression( + expression = + "ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && FILESET::OWNER", + accessMetadataType = MetadataObject.Type.FILESET) public Response dropFileset( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("fileset") String fileset) { + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("fileset") @AuthorizationMetadata(type = MetadataObject.Type.FILESET) + String fileset) { LOG.info("Received drop fileset request: {}.{}.{}.{}", metalake, catalog, schema, fileset); try { return Utils.doAs( @@ -308,11 +360,17 @@ public class FilesetOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "get-file-location." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "get-file-location", absolute = true) + @AuthorizationExpression( + expression = loadFilesetAuthorizationExpression, + accessMetadataType = MetadataObject.Type.FILESET) public Response getFileLocation( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("fileset") String fileset, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("fileset") @AuthorizationMetadata(type = MetadataObject.Type.FILESET) + String fileset, @QueryParam("sub_path") @NotNull String subPath, @QueryParam("location_name") String locationName) { LOG.info( diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java index 455165ec47..b5c5ff7e66 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/ModelOperations.java @@ -20,6 +20,7 @@ package org.apache.gravitino.server.web.rest; import com.codahale.metrics.annotation.ResponseMetered; import com.codahale.metrics.annotation.Timed; +import java.util.Arrays; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.DELETE; @@ -33,6 +34,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; +import org.apache.gravitino.Entity; +import org.apache.gravitino.MetadataObject; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; import org.apache.gravitino.catalog.ModelDispatcher; @@ -55,6 +58,9 @@ import org.apache.gravitino.model.Model; import org.apache.gravitino.model.ModelChange; import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.model.ModelVersionChange; +import org.apache.gravitino.server.authorization.MetadataFilterHelper; +import org.apache.gravitino.server.authorization.annotations.AuthorizationExpression; +import org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata; import org.apache.gravitino.server.web.Utils; import org.apache.gravitino.utils.NameIdentifierUtil; import org.apache.gravitino.utils.NamespaceUtil; @@ -66,6 +72,11 @@ public class ModelOperations { private static final Logger LOG = LoggerFactory.getLogger(ModelOperations.class); + private static final String loadModelAuthorizationExpression = + "ANY(OWNER, METALAKE, CATALOG) ||" + + " SCHEMA_OWNER_WITH_USE_CATALOG || " + + " ANY_USE_CATALOG && ANY_USE_SCHEMA && (MODEL::OWNER || ANY_USE_MODEL)"; + private final ModelDispatcher modelDispatcher; @Context private HttpServletRequest httpRequest; @@ -92,6 +103,9 @@ public class ModelOperations { () -> { NameIdentifier[] modelIds = modelDispatcher.listModels(modelNs); modelIds = modelIds == null ? new NameIdentifier[0] : modelIds; + modelIds = + MetadataFilterHelper.filterByExpression( + metalake, loadModelAuthorizationExpression, Entity.EntityType.MODEL, modelIds); LOG.info("List {} models under schema {}", modelIds.length, modelNs); return Utils.ok(new EntityListResponse(modelIds)); }); @@ -106,11 +120,16 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "get-model." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "get-model", absolute = true) + @AuthorizationExpression( + expression = loadModelAuthorizationExpression, + accessMetadataType = MetadataObject.Type.MODEL) public Response getModel( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model) { + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model) { LOG.info("Received get model request: {}.{}.{}.{}", metalake, catalog, schema, model); NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, schema, model); @@ -132,10 +151,18 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "register-model." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "register-model", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && ANY_CREATE_MODEL", + accessMetadataType = MetadataObject.Type.SCHEMA) public Response registerModel( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, ModelRegisterRequest request) { LOG.info( "Received register model request: {}.{}.{}.{}", @@ -170,11 +197,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "delete-model." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "delete-model", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && MODEL::OWNER", + accessMetadataType = MetadataObject.Type.MODEL) public Response deleteModel( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model) { + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model) { LOG.info("Received delete model request: {}.{}.{}.{}", metalake, catalog, schema, model); NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, schema, model); @@ -218,12 +253,48 @@ public class ModelOperations { if (verbose) { ModelVersion[] modelVersions = modelDispatcher.listModelVersionInfos(modelId); modelVersions = modelVersions == null ? new ModelVersion[0] : modelVersions; + modelVersions = + Arrays.stream(modelVersions) + .filter( + modelVersion -> { + NameIdentifier[] nameIdentifiers = + new NameIdentifier[] { + NameIdentifierUtil.ofModelVersion( + metalake, catalog, schema, model, modelVersion.version()) + }; + return MetadataFilterHelper.filterByExpression( + metalake, + loadModelAuthorizationExpression, + Entity.EntityType.MODEL_VERSION, + nameIdentifiers) + .length + > 0; + }) + .toArray(ModelVersion[]::new); LOG.info("List {} versions of model {}", modelVersions.length, modelId); return Utils.ok( new ModelVersionInfoListResponse(DTOConverters.toDTOs(modelVersions))); } else { int[] versions = modelDispatcher.listModelVersions(modelId); versions = versions == null ? new int[0] : versions; + versions = + Arrays.stream(versions) + .filter( + modelVersion -> { + NameIdentifier[] nameIdentifiers = + new NameIdentifier[] { + NameIdentifierUtil.ofModelVersion( + metalake, catalog, schema, model, modelVersion) + }; + return MetadataFilterHelper.filterByExpression( + metalake, + loadModelAuthorizationExpression, + Entity.EntityType.MODEL_VERSION, + nameIdentifiers) + .length + > 0; + }) + .toArray(); LOG.info("List {} versions of model {}", versions.length, modelId); return Utils.ok(new ModelVersionListResponse(versions)); } @@ -239,11 +310,16 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "get-model-version." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "get-model-version", absolute = true) + @AuthorizationExpression( + expression = loadModelAuthorizationExpression, + accessMetadataType = MetadataObject.Type.MODEL) public Response getModelVersion( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, @PathParam("version") int version) { LOG.info( "Received get model version request: {}.{}.{}.{}.{}", @@ -274,11 +350,16 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "get-model-alias." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "get-model-alias", absolute = true) + @AuthorizationExpression( + expression = loadModelAuthorizationExpression, + accessMetadataType = MetadataObject.Type.MODEL) public Response getModelVersionByAlias( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, @PathParam("alias") String alias) { LOG.info( "Received get model version alias request: {}.{}.{}.{}.{}", @@ -309,11 +390,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "link-model-version." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "link-model-version", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && (MODEL::OWNER || ANY_USE_MODEL && ANY_CREATE_MODEL_VERSION)", + accessMetadataType = MetadataObject.Type.MODEL) public Response linkModelVersion( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, ModelVersionLinkRequest request) { LOG.info("Received link model version request: {}.{}.{}.{}", metalake, catalog, schema, model); NameIdentifier modelId = NameIdentifierUtil.ofModel(metalake, catalog, schema, model); @@ -344,11 +433,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "delete-model-version." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "delete-model-version", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && MODEL::OWNER", + accessMetadataType = MetadataObject.Type.MODEL) public Response deleteModelVersion( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, @PathParam("version") int version) { LOG.info( "Received delete model version request: {}.{}.{}.{}.{}", @@ -384,11 +481,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "delete-model-alias." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "delete-model-alias", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && MODEL::OWNER", + accessMetadataType = MetadataObject.Type.MODEL) public Response deleteModelVersionByAlias( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, @PathParam("alias") String alias) { LOG.info( "Received delete model version by alias request: {}.{}.{}.{}.{}", @@ -425,11 +530,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "alter-model-version." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "alter-model-version", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && MODEL::OWNER", + accessMetadataType = MetadataObject.Type.MODEL) public Response alterModelVersion( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, @PathParam("version") int version, ModelVersionUpdatesRequest request) { LOG.info( @@ -471,11 +584,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "alter-model-alias." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "alter-model-alias", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && MODEL::OWNER", + accessMetadataType = MetadataObject.Type.MODEL) public Response alterModelVersionByAlias( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, @PathParam("alias") String alias, ModelVersionUpdatesRequest request) { LOG.info( @@ -516,11 +637,19 @@ public class ModelOperations { @Produces("application/vnd.gravitino.v1+json") @Timed(name = "alter-model." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) @ResponseMetered(name = "alter-model", absolute = true) + @AuthorizationExpression( + expression = + " ANY(OWNER, METALAKE, CATALOG) || " + + "SCHEMA_OWNER_WITH_USE_CATALOG || " + + "ANY_USE_CATALOG && ANY_USE_SCHEMA && MODEL::OWNER", + accessMetadataType = MetadataObject.Type.MODEL) public Response alterModel( - @PathParam("metalake") String metalake, - @PathParam("catalog") String catalog, - @PathParam("schema") String schema, - @PathParam("model") String model, + @PathParam("metalake") @AuthorizationMetadata(type = MetadataObject.Type.METALAKE) + String metalake, + @PathParam("catalog") @AuthorizationMetadata(type = MetadataObject.Type.CATALOG) + String catalog, + @PathParam("schema") @AuthorizationMetadata(type = MetadataObject.Type.SCHEMA) String schema, + @PathParam("model") @AuthorizationMetadata(type = MetadataObject.Type.MODEL) String model, ModelUpdatesRequest request) { LOG.info("Received alter model request: {}.{}.{}.{}", metalake, catalog, schema, model); try {
