This is an automated email from the ASF dual-hosted git repository.
roryqi 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 9d3366b7db [#8814] feat(authz): Supports job API access control (#9283)
9d3366b7db is described below
commit 9d3366b7db239b84f9f10c2cd68f86b652307468
Author: roryqi <[email protected]>
AuthorDate: Tue Dec 2 14:12:17 2025 +0800
[#8814] feat(authz): Supports job API access control (#9283)
### What changes were proposed in this pull request?
Supports job API access control
### Why are the changes needed?
Fix: #8814
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Added IT.
---
.../test/authorization/JobAuthorizationIT.java | 429 +++++++++++++++++++++
.../java/org/apache/gravitino/GravitinoEnv.java | 10 +-
.../authorization/AuthorizationUtils.java | 13 +-
.../apache/gravitino/cache/ReverseIndexRules.java | 4 +
.../apache/gravitino/hook/JobHookDispatcher.java | 132 +++++++
.../org/apache/gravitino/policy/PolicyManager.java | 12 +-
.../RelationalEntityStoreIdResolver.java | 32 +-
.../relational/mapper/JobTemplateMetaMapper.java | 9 +
.../mapper/JobTemplateMetaSQLProviderFactory.java | 9 +
.../base/JobTemplateMetaBaseSQLProvider.java | 20 +
.../relational/service/JobTemplateMetaService.java | 15 +
.../relational/service/MetadataObjectService.java | 67 +++-
.../apache/gravitino/utils/MetadataObjectUtil.java | 33 +-
.../apache/gravitino/utils/NameIdentifierUtil.java | 32 ++
.../org/apache/gravitino/utils/NamespaceUtil.java | 24 ++
.../server/authorization/MetadataIdConverter.java | 4 +
.../annotations/AuthorizationRequest.java | 3 +-
.../AuthorizationExpressionConverter.java | 5 +
.../web/filter/GravitinoInterceptionService.java | 4 +-
.../gravitino/server/web/filter/ParameterUtil.java | 15 +
.../authorization/AuthorizeExecutorFactory.java | 7 +
.../authorization/RunJobAuthorizationExecutor.java | 78 ++++
.../gravitino/server/web/rest/JobOperations.java | 85 +++-
23 files changed, 984 insertions(+), 58 deletions(-)
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/JobAuthorizationIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/JobAuthorizationIT.java
new file mode 100644
index 0000000000..befd664c2e
--- /dev/null
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/JobAuthorizationIT.java
@@ -0,0 +1,429 @@
+/*
+ * 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.integration.test.authorization;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.io.FileUtils;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.authorization.Privileges;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.dto.MetalakeDTO;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.job.JobHandle;
+import org.apache.gravitino.job.JobTemplate;
+import org.apache.gravitino.job.ShellJobTemplate;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class JobAuthorizationIT extends BaseRestApiAuthorizationIT {
+
+ private static File testStagingDir;
+ private static ShellJobTemplate.Builder builder;
+ private static final String ROLE = "job_test_role";
+
+ @BeforeAll
+ public void startIntegrationTest() throws Exception {
+ testStagingDir = Files.createTempDirectory("test_staging_dir").toFile();
+ String testEntryScriptPath = generateTestEntryScript();
+ String testLibScriptPath = generateTestLibScript();
+
+ builder =
+ ShellJobTemplate.builder()
+ .withComment("Test shell job template")
+ .withExecutable(testEntryScriptPath)
+ .withArguments(Lists.newArrayList("{{arg1}}", "{{arg2}}"))
+ .withEnvironments(ImmutableMap.of("ENV_VAR", "{{env_var}}"))
+ .withScripts(Lists.newArrayList(testLibScriptPath))
+ .withCustomFields(Collections.emptyMap());
+
+ Map<String, String> configs =
+ ImmutableMap.of(
+ "gravitino.job.stagingDir",
+ testStagingDir.getAbsolutePath(),
+ "gravitino.job.statusPullIntervalInMs",
+ "3000");
+ registerCustomConfigs(configs);
+ super.startIntegrationTest();
+
+ // Create role for authorization tests
+ GravitinoMetalake metalake = client.loadMetalake(METALAKE);
+ metalake.createRole(ROLE, new HashMap<>(), Collections.emptyList());
+ metalake.grantRolesToUser(ImmutableList.of(ROLE), NORMAL_USER);
+ }
+
+ @Test
+ @Order(1)
+ public void testRegisterJobTemplate() {
+ // User without privilege cannot register job template
+ assertThrows(
+ ForbiddenException.class,
+ () ->
+ normalUserClient
+ .loadMetalake(METALAKE)
+ .registerJobTemplate(builder.withName("test1").build()));
+
+ // Grant privilege to normal user
+ GravitinoMetalake metalake = client.loadMetalake(METALAKE);
+ metalake.grantPrivilegesToRole(
+ ROLE,
+ MetadataObjects.of(null, METALAKE, MetadataObject.Type.METALAKE),
+ ImmutableList.of(Privileges.RegisterJobTemplate.allow()));
+
+ // Now normal user can register job templates
+ JobTemplate template1 = builder.withName("test_1").build();
+ JobTemplate template2 = builder.withName("test_2").build();
+ normalUserClient.loadMetalake(METALAKE).registerJobTemplate(template1);
+ normalUserClient.loadMetalake(METALAKE).registerJobTemplate(template2);
+
+ // Admin can always register job templates
+ JobTemplate template3 = builder.withName("test_3").build();
+ metalake.registerJobTemplate(template3);
+ }
+
+ @Test
+ @Order(2)
+ public void testListJobTemplates() {
+ // Normal user can see job templates they own (test_1, test_2)
+ List<JobTemplate> normalUserTemplates =
+ normalUserClient.loadMetalake(METALAKE).listJobTemplates();
+ Assertions.assertEquals(2, normalUserTemplates.size());
+
+ // Admin can see all job templates (test_1, test_2, test_3)
+ List<JobTemplate> adminTemplates =
client.loadMetalake(METALAKE).listJobTemplates();
+ Assertions.assertEquals(3, adminTemplates.size());
+ }
+
+ @Test
+ @Order(3)
+ public void testGetJobTemplate() {
+ // Normal user can get their own job template
+ JobTemplate template =
normalUserClient.loadMetalake(METALAKE).getJobTemplate("test_1");
+ Assertions.assertNotNull(template);
+ Assertions.assertEquals("test_1", template.name());
+
+ // Normal user cannot get job template owned by admin
+ assertThrows(
+ ForbiddenException.class,
+ () ->
normalUserClient.loadMetalake(METALAKE).getJobTemplate("test_3"));
+
+ // Admin can get any job template
+ JobTemplate adminTemplate =
client.loadMetalake(METALAKE).getJobTemplate("test_3");
+ Assertions.assertNotNull(adminTemplate);
+ }
+
+ @Test
+ @Order(4)
+ public void testDeleteJobTemplate() {
+ // Normal user cannot delete job template owned by admin
+ assertThrows(
+ ForbiddenException.class,
+ () ->
normalUserClient.loadMetalake(METALAKE).deleteJobTemplate("test_3"));
+
+ // Normal user can delete their own job template
+ boolean deleted =
normalUserClient.loadMetalake(METALAKE).deleteJobTemplate("test_1");
+ Assertions.assertTrue(deleted);
+
+ // Admin can delete any job template
+ boolean adminDeleted =
client.loadMetalake(METALAKE).deleteJobTemplate("test_3");
+ Assertions.assertTrue(adminDeleted);
+ }
+
+ @Test
+ @Order(5)
+ public void testRunJob() {
+ // Normal user without RunJob privilege cannot run jobs
+ assertThrows(
+ ForbiddenException.class,
+ () ->
+ normalUserClient
+ .loadMetalake(METALAKE)
+ .runJob(
+ "test_2",
+ ImmutableMap.of("arg1", "value1", "arg2", "success",
"env_var", "value2")));
+
+ // Grant RunJob privilege to normal user but not UseJobTemplate privilege
+ GravitinoMetalake metalake = client.loadMetalake(METALAKE);
+ metalake.grantPrivilegesToRole(
+ ROLE,
+ MetadataObjects.of(null, METALAKE, MetadataObject.Type.METALAKE),
+ ImmutableList.of(Privileges.RunJob.allow()));
+
+ // User with RunJob privilege but without UseJobTemplate privilege cannot
run jobs
+ // The authorization expression is: METALAKE::OWNER || (METALAKE::RUN_JOB
&&
+ // (ANY_USE_JOB_TEMPLATE || JOB_TEMPLATE::OWNER))
+
client.loadMetalake(METALAKE).registerJobTemplate(builder.withName("test_4").build());
+ assertThrows(
+ ForbiddenException.class,
+ () ->
+ normalUserClient
+ .loadMetalake(METALAKE)
+ .runJob(
+ "test_4",
+ ImmutableMap.of("arg1", "value1", "arg2", "success",
"env_var", "value2")));
+
+ // Grant UseJobTemplate privilege on the metalake to normal user
+ metalake.grantPrivilegesToRole(
+ ROLE,
+ MetadataObjects.of(null, "test_2", MetadataObject.Type.JOB_TEMPLATE),
+ ImmutableList.of(Privileges.UseJobTemplate.allow()));
+ Assertions.assertDoesNotThrow(() -> metalake.getRole(ROLE));
+
+ // Now normal user can run jobs on their own template (has both RunJob and
UseJobTemplate)
+ JobHandle normalUserJobHandle =
+ normalUserClient
+ .loadMetalake(METALAKE)
+ .runJob(
+ "test_2",
+ ImmutableMap.of("arg1", "value1", "arg2", "success",
"env_var", "value2"));
+ Assertions.assertNotNull(normalUserJobHandle);
+ Assertions.assertEquals("test_2", normalUserJobHandle.jobTemplateName());
+
+ // Admin can run jobs on any template
+ JobHandle adminJobHandle =
+ metalake.runJob(
+ "test_2", ImmutableMap.of("arg1", "value3", "arg2", "success",
"env_var", "value4"));
+ Assertions.assertNotNull(adminJobHandle);
+ }
+
+ @Test
+ @Order(6)
+ public void testListJob() {
+ // Normal user can see jobs they own (1 job from test_2 template)
+ List<JobHandle> normalUserJobs =
normalUserClient.loadMetalake(METALAKE).listJobs();
+ Assertions.assertEquals(1, normalUserJobs.size());
+
+ // Admin can see all jobs (2 jobs total)
+ List<JobHandle> adminJobs = client.loadMetalake(METALAKE).listJobs();
+ Assertions.assertEquals(2, adminJobs.size());
+
+ // Listing jobs for specific template shows all jobs from that template
+ List<JobHandle> test2Jobs =
normalUserClient.loadMetalake(METALAKE).listJobs("test_2");
+ Assertions.assertEquals(1, test2Jobs.size());
+ }
+
+ @Test
+ @Order(7)
+ public void testGetJob() {
+ // Get job IDs for testing
+ List<JobHandle> normalUserJobs =
normalUserClient.loadMetalake(METALAKE).listJobs();
+ Assertions.assertTrue(normalUserJobs.size() > 0);
+ String normalUserJobId = normalUserJobs.get(0).jobId();
+
+ List<JobHandle> adminJobs = client.loadMetalake(METALAKE).listJobs();
+ String adminJobId = null;
+ for (JobHandle job : adminJobs) {
+ if (!job.jobId().equals(normalUserJobId)) {
+ adminJobId = job.jobId();
+ break;
+ }
+ }
+ Assertions.assertNotNull(adminJobId);
+
+ // Normal user can get their own job
+ JobHandle job =
normalUserClient.loadMetalake(METALAKE).getJob(normalUserJobId);
+ Assertions.assertNotNull(job);
+ Assertions.assertEquals(normalUserJobId, job.jobId());
+
+ // Normal user cannot get job owned by admin
+ String finalAdminJobId = adminJobId;
+ assertThrows(
+ ForbiddenException.class,
+ () -> normalUserClient.loadMetalake(METALAKE).getJob(finalAdminJobId));
+
+ // Admin can get any job
+ JobHandle adminJob = client.loadMetalake(METALAKE).getJob(adminJobId);
+ Assertions.assertNotNull(adminJob);
+ }
+
+ @Test
+ @Order(8)
+ public void testCancelJob() {
+ // Get job IDs for testing
+ List<JobHandle> normalUserJobs =
normalUserClient.loadMetalake(METALAKE).listJobs();
+ Assertions.assertTrue(normalUserJobs.size() > 0);
+ String normalUserJobId = normalUserJobs.get(0).jobId();
+
+ List<JobHandle> adminJobs = client.loadMetalake(METALAKE).listJobs();
+ String adminJobId = null;
+ for (JobHandle job : adminJobs) {
+ if (!job.jobId().equals(normalUserJobId)) {
+ adminJobId = job.jobId();
+ break;
+ }
+ }
+ Assertions.assertNotNull(adminJobId);
+
+ // Normal user cannot cancel job owned by admin
+ String finalAdminJobId = adminJobId;
+ assertThrows(
+ ForbiddenException.class,
+ () ->
normalUserClient.loadMetalake(METALAKE).cancelJob(finalAdminJobId));
+
+ // Normal user can cancel their own job
+ JobHandle canceledJob =
normalUserClient.loadMetalake(METALAKE).cancelJob(normalUserJobId);
+ Assertions.assertNotNull(canceledJob);
+
+ // Admin can cancel any job
+ JobHandle adminCanceledJob =
client.loadMetalake(METALAKE).cancelJob(adminJobId);
+ Assertions.assertNotNull(adminCanceledJob);
+ }
+
+ @Test
+ @Order(9)
+ public void testJobOperationsWithNonExistentMetalake() throws Exception {
+ // Test that all job operations with @AuthorizationExpression return 403
Forbidden
+ // when the metalake doesn't exist, instead of inconsistent 404 responses
+ String nonExistentMetalake = "nonExistentMetalake";
+
+ // Access the restClient from normalUserClient using reflection
+ Method restClientMethod =
+
normalUserClient.getClass().getSuperclass().getDeclaredMethod("restClient");
+ restClientMethod.setAccessible(true);
+ Object restClient = restClientMethod.invoke(normalUserClient);
+
+ // Create a MetalakeDTO for the non-existent metalake
+ MetalakeDTO metalakeDTO =
+ MetalakeDTO.builder()
+ .withName(nonExistentMetalake)
+ .withComment("test")
+ .withProperties(Maps.newHashMap())
+ .withAudit(
+ org.apache.gravitino.dto.AuditDTO.builder()
+ .withCreator("test")
+ .withCreateTime(java.time.Instant.now())
+ .build())
+ .build();
+
+ // Use DTOConverters.toMetaLake() via reflection to create
GravitinoMetalake
+ Class<?> dtoConvertersClass =
Class.forName("org.apache.gravitino.client.DTOConverters");
+ Method toMetaLakeMethod =
+ dtoConvertersClass.getDeclaredMethod(
+ "toMetaLake",
+ MetalakeDTO.class,
+ Class.forName("org.apache.gravitino.client.RESTClient"));
+ toMetaLakeMethod.setAccessible(true);
+ GravitinoMetalake nonExistentMetalakeObj =
+ (GravitinoMetalake) toMetaLakeMethod.invoke(null, metalakeDTO,
restClient);
+
+ // Test registerJobTemplate - should return 403 ForbiddenException
+ assertThrows(
+ ForbiddenException.class,
+ () ->
nonExistentMetalakeObj.registerJobTemplate(builder.withName("testTemplate").build()));
+
+ // Test listJobTemplates - should return 403 ForbiddenException
+ assertThrows(ForbiddenException.class,
nonExistentMetalakeObj::listJobTemplates);
+
+ // Test getJobTemplate - should return 403 ForbiddenException
+ assertThrows(
+ ForbiddenException.class, () ->
nonExistentMetalakeObj.getJobTemplate("testTemplate"));
+
+ // Test deleteJobTemplate - should return 403 ForbiddenException
+ assertThrows(
+ ForbiddenException.class, () ->
nonExistentMetalakeObj.deleteJobTemplate("testTemplate"));
+
+ // Test runJob - should return 403 ForbiddenException
+ assertThrows(
+ ForbiddenException.class,
+ () -> nonExistentMetalakeObj.runJob("testTemplate",
Maps.newHashMap()));
+
+ // Test listJobs - should return 403 ForbiddenException
+ assertThrows(ForbiddenException.class, nonExistentMetalakeObj::listJobs);
+
+ // Test listJobs with template name - should return 403 ForbiddenException
+ assertThrows(ForbiddenException.class, () ->
nonExistentMetalakeObj.listJobs("testTemplate"));
+
+ // Test getJob - should return 403 ForbiddenException
+ assertThrows(ForbiddenException.class, () ->
nonExistentMetalakeObj.getJob("testJobId"));
+
+ // Test cancelJob - should return 403 ForbiddenException
+ assertThrows(ForbiddenException.class, () ->
nonExistentMetalakeObj.cancelJob("testJobId"));
+ }
+
+ @AfterAll
+ public static void tearDown() throws Exception {
+ if (testStagingDir != null && testStagingDir.exists()) {
+ FileUtils.deleteDirectory(testStagingDir);
+ }
+ }
+
+ private static String generateTestEntryScript() {
+ String content =
+ "#!/bin/bash\n"
+ + "echo \"starting test job\"\n\n"
+ + "bin=\"$(dirname \"${BASH_SOURCE-$0}\")\"\n"
+ + "bin=\"$(cd \"${bin}\">/dev/null; pwd)\"\n\n"
+ + ". \"${bin}/common.sh\"\n\n"
+ + "sleep 3\n\n"
+ + "JOB_NAME=\"test_job-$(date +%s)-$1\"\n\n"
+ + "echo \"Submitting job with name: $JOB_NAME\"\n\n"
+ + "echo \"$1\"\n\n"
+ + "echo \"$2\"\n\n"
+ + "echo \"$ENV_VAR\"\n\n"
+ + "if [[ \"$2\" == \"success\" ]]; then\n"
+ + " exit 0\n"
+ + "elif [[ \"$2\" == \"fail\" ]]; then\n"
+ + " exit 1\n"
+ + "else\n"
+ + " exit 2\n"
+ + "fi\n";
+
+ try {
+ File scriptFile = new File(testStagingDir, "test-job.sh");
+ Files.writeString(scriptFile.toPath(), content);
+ if (!scriptFile.setExecutable(true)) {
+ throw new RuntimeException("Failed to set script as executable");
+ }
+ return scriptFile.getAbsolutePath();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create test entry script", e);
+ }
+ }
+
+ private static String generateTestLibScript() {
+ String content = "#!/bin/bash\necho \"in common script\"\n";
+
+ try {
+ File scriptFile = new File(testStagingDir, "common.sh");
+ Files.writeString(scriptFile.toPath(), content);
+ if (!scriptFile.setExecutable(true)) {
+ throw new RuntimeException("Failed to set script as executable");
+ }
+ return scriptFile.getAbsolutePath();
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to create test lib script", e);
+ }
+ }
+}
diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index 318d07a624..34d5506420 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -53,6 +53,7 @@ import
org.apache.gravitino.credential.CredentialOperationDispatcher;
import org.apache.gravitino.hook.AccessControlHookDispatcher;
import org.apache.gravitino.hook.CatalogHookDispatcher;
import org.apache.gravitino.hook.FilesetHookDispatcher;
+import org.apache.gravitino.hook.JobHookDispatcher;
import org.apache.gravitino.hook.MetalakeHookDispatcher;
import org.apache.gravitino.hook.ModelHookDispatcher;
import org.apache.gravitino.hook.PolicyHookDispatcher;
@@ -603,15 +604,16 @@ public class GravitinoEnv {
this.auxServiceManager.serviceInit(config);
// Create and initialize Tag related modules
- TagEventDispatcher tagEventDispatcher =
- new TagEventDispatcher(eventBus, new TagManager(idGenerator,
entityStore));
- this.tagDispatcher = new TagHookDispatcher(tagEventDispatcher);
+ TagManager tagManager = new TagManager(idGenerator, entityStore);
+ TagHookDispatcher tagHookDispatcher = new TagHookDispatcher(tagManager);
+ this.tagDispatcher = new TagEventDispatcher(eventBus, tagHookDispatcher);
PolicyEventDispatcher policyEventDispatcher =
new PolicyEventDispatcher(eventBus, new PolicyManager(idGenerator,
entityStore));
this.policyDispatcher = new PolicyHookDispatcher(policyEventDispatcher);
this.jobOperationDispatcher =
- new JobEventDispatcher(eventBus, new JobManager(config, entityStore,
idGenerator));
+ new JobEventDispatcher(
+ eventBus, new JobHookDispatcher(new JobManager(config,
entityStore, idGenerator)));
}
}
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 6fcd13121c..cd15d98904 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -66,6 +66,14 @@ public class AuthorizationUtils {
static final String USER_DOES_NOT_EXIST_MSG = "User %s does not exist in the
metalake %s";
static final String GROUP_DOES_NOT_EXIST_MSG = "Group %s does not exist in
the metalake %s";
static final String ROLE_DOES_NOT_EXIST_MSG = "Role %s does not exist in the
metalake %s";
+ private static final Set<MetadataObject.Type> SKIP_APPLY_TYPES =
+ Sets.newHashSet(
+ MetadataObject.Type.ROLE,
+ MetadataObject.Type.METALAKE,
+ MetadataObject.Type.JOB,
+ MetadataObject.Type.JOB_TEMPLATE,
+ MetadataObject.Type.TAG,
+ MetadataObject.Type.POLICY);
private static final Set<Privilege.Name> FILESET_PRIVILEGES =
Sets.immutableEnumSet(
@@ -357,10 +365,7 @@ public class AuthorizationUtils {
}
private static boolean needApplyAuthorization(MetadataObject.Type type) {
- return type != MetadataObject.Type.ROLE
- && type != MetadataObject.Type.METALAKE
- && type != MetadataObject.Type.TAG
- && type != MetadataObject.Type.POLICY;
+ return !SKIP_APPLY_TYPES.contains(type);
}
private static void callAuthorizationPluginImpl(
diff --git
a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
index ace2fb044d..d457daa092 100644
--- a/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
+++ b/core/src/main/java/org/apache/gravitino/cache/ReverseIndexRules.java
@@ -165,6 +165,10 @@ public class ReverseIndexRules {
entityType = Entity.EntityType.POLICY;
namespace =
NamespaceUtil.ofPolicy(roleEntity.namespace().level(0));
break;
+ case JOB_TEMPLATE:
+ entityType = Entity.EntityType.JOB_TEMPLATE;
+ namespace =
NamespaceUtil.ofJobTemplate(roleEntity.namespace().level(0));
+ break;
default:
throw new UnsupportedOperationException(
"Don't support securable object type: " +
securableObject.type());
diff --git
a/core/src/main/java/org/apache/gravitino/hook/JobHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/JobHookDispatcher.java
new file mode 100644
index 0000000000..a6dac9bbd9
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/hook/JobHookDispatcher.java
@@ -0,0 +1,132 @@
+/*
+ * 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.hook;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.authorization.AuthorizationUtils;
+import org.apache.gravitino.authorization.Owner;
+import org.apache.gravitino.authorization.OwnerDispatcher;
+import org.apache.gravitino.exceptions.InUseException;
+import org.apache.gravitino.exceptions.JobTemplateAlreadyExistsException;
+import org.apache.gravitino.exceptions.NoSuchJobException;
+import org.apache.gravitino.exceptions.NoSuchJobTemplateException;
+import org.apache.gravitino.job.JobOperationDispatcher;
+import org.apache.gravitino.job.JobTemplateChange;
+import org.apache.gravitino.meta.JobEntity;
+import org.apache.gravitino.meta.JobTemplateEntity;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+public class JobHookDispatcher implements JobOperationDispatcher {
+ private final JobOperationDispatcher jobOperationDispatcher;
+
+ public JobHookDispatcher(JobOperationDispatcher jobOperationDispatcher) {
+ this.jobOperationDispatcher = jobOperationDispatcher;
+ }
+
+ @Override
+ public List<JobTemplateEntity> listJobTemplates(String metalake) {
+ return jobOperationDispatcher.listJobTemplates(metalake);
+ }
+
+ @Override
+ public void registerJobTemplate(String metalake, JobTemplateEntity
jobTemplateEntity)
+ throws JobTemplateAlreadyExistsException {
+ // Check whether the current user exists or not
+ AuthorizationUtils.checkCurrentUser(metalake,
PrincipalUtils.getCurrentUserName());
+
+ jobOperationDispatcher.registerJobTemplate(metalake, jobTemplateEntity);
+
+ // Set the creator as the owner of the job template.
+ OwnerDispatcher ownerManager =
GravitinoEnv.getInstance().ownerDispatcher();
+ if (ownerManager != null) {
+ ownerManager.setOwner(
+ metalake,
+ NameIdentifierUtil.toMetadataObject(
+ jobTemplateEntity.nameIdentifier(),
Entity.EntityType.JOB_TEMPLATE),
+ PrincipalUtils.getCurrentUserName(),
+ Owner.Type.USER);
+ }
+ }
+
+ @Override
+ public JobTemplateEntity getJobTemplate(String metalake, String
jobTemplateName)
+ throws NoSuchJobTemplateException {
+ return jobOperationDispatcher.getJobTemplate(metalake, jobTemplateName);
+ }
+
+ @Override
+ public boolean deleteJobTemplate(String metalake, String jobTemplateName)
throws InUseException {
+ return jobOperationDispatcher.deleteJobTemplate(metalake, jobTemplateName);
+ }
+
+ @Override
+ public JobTemplateEntity alterJobTemplate(
+ String metalake, String jobTemplateName, JobTemplateChange... changes)
+ throws NoSuchJobTemplateException, IllegalArgumentException {
+ return jobOperationDispatcher.alterJobTemplate(metalake, jobTemplateName,
changes);
+ }
+
+ @Override
+ public List<JobEntity> listJobs(String metalake, Optional<String>
jobTemplateName)
+ throws NoSuchJobTemplateException {
+ return jobOperationDispatcher.listJobs(metalake, jobTemplateName);
+ }
+
+ @Override
+ public JobEntity getJob(String metalake, String jobId) throws
NoSuchJobException {
+ return jobOperationDispatcher.getJob(metalake, jobId);
+ }
+
+ @Override
+ public JobEntity runJob(String metalake, String jobTemplateName, Map<String,
String> jobConf)
+ throws NoSuchJobTemplateException {
+ // Check whether the current user exists or not
+ AuthorizationUtils.checkCurrentUser(metalake,
PrincipalUtils.getCurrentUserName());
+
+ JobEntity jobEntity = jobOperationDispatcher.runJob(metalake,
jobTemplateName, jobConf);
+
+ // Set the creator as the owner of the job.
+ OwnerDispatcher ownerManager =
GravitinoEnv.getInstance().ownerDispatcher();
+ if (ownerManager != null) {
+ ownerManager.setOwner(
+ metalake,
+ NameIdentifierUtil.toMetadataObject(jobEntity.nameIdentifier(),
Entity.EntityType.JOB),
+ PrincipalUtils.getCurrentUserName(),
+ Owner.Type.USER);
+ }
+
+ return jobEntity;
+ }
+
+ @Override
+ public JobEntity cancelJob(String metalake, String jobId) throws
NoSuchJobException {
+ return jobOperationDispatcher.cancelJob(metalake, jobId);
+ }
+
+ @Override
+ public void close() throws IOException {
+ jobOperationDispatcher.close();
+ }
+}
diff --git a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
index fe39649b9d..bcb8bb0439 100644
--- a/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
+++ b/core/src/main/java/org/apache/gravitino/policy/PolicyManager.java
@@ -56,6 +56,14 @@ import org.slf4j.LoggerFactory;
public class PolicyManager implements PolicyDispatcher {
private static final Logger LOG =
LoggerFactory.getLogger(PolicyManager.class);
+ private static final Set<MetadataObject.Type>
SUPPORTED_METADATA_OBJECT_TYPES_FOR_POLICIES =
+ Sets.newHashSet(
+ MetadataObject.Type.CATALOG,
+ MetadataObject.Type.SCHEMA,
+ MetadataObject.Type.TABLE,
+ MetadataObject.Type.FILESET,
+ MetadataObject.Type.TOPIC,
+ MetadataObject.Type.MODEL);
private final IdGenerator idGenerator;
private final EntityStore entityStore;
@@ -295,9 +303,7 @@ public class PolicyManager implements PolicyDispatcher {
String[] policiesToAdd,
String[] policiesToRemove) {
Preconditions.checkArgument(
- !metadataObject.type().equals(MetadataObject.Type.METALAKE)
- && !metadataObject.type().equals(MetadataObject.Type.ROLE)
- && !metadataObject.type().equals(MetadataObject.Type.COLUMN),
+
SUPPORTED_METADATA_OBJECT_TYPES_FOR_POLICIES.contains(metadataObject.type()),
"Cannot associate policies for unsupported metadata object type %s",
metadataObject.type());
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStoreIdResolver.java
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStoreIdResolver.java
index 5794eeb358..1f32e25658 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStoreIdResolver.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStoreIdResolver.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.apache.gravitino.Entity;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.job.JobHandle;
import org.apache.gravitino.meta.EntityIdResolver;
import org.apache.gravitino.meta.NamespacedEntityId;
import org.apache.gravitino.storage.relational.helper.CatalogIds;
@@ -29,6 +30,7 @@ import
org.apache.gravitino.storage.relational.helper.SchemaIds;
import org.apache.gravitino.storage.relational.service.CatalogMetaService;
import org.apache.gravitino.storage.relational.service.FilesetMetaService;
import org.apache.gravitino.storage.relational.service.GroupMetaService;
+import org.apache.gravitino.storage.relational.service.JobTemplateMetaService;
import org.apache.gravitino.storage.relational.service.MetalakeMetaService;
import org.apache.gravitino.storage.relational.service.ModelMetaService;
import org.apache.gravitino.storage.relational.service.PolicyMetaService;
@@ -49,10 +51,14 @@ public class RelationalEntityStoreIdResolver implements
EntityIdResolver {
Entity.EntityType.USER,
Entity.EntityType.GROUP,
Entity.EntityType.TAG,
- Entity.EntityType.POLICY);
- private static final Set<Entity.EntityType>
ENTITY_TYPES_REQURING_CATALOG_IDS =
+ Entity.EntityType.POLICY,
+ Entity.EntityType.JOB,
+ Entity.EntityType.JOB_TEMPLATE);
+
+ private static final Set<Entity.EntityType>
ENTITY_TYPES_REQUIRING_CATALOG_IDS =
ImmutableSet.of(Entity.EntityType.CATALOG);
- private static final Set<Entity.EntityType> ENTITY_TYPES_REQURING_SCHEMA_IDS
=
+
+ private static final Set<Entity.EntityType>
ENTITY_TYPES_REQUIRING_SCHEMA_IDS =
ImmutableSet.of(
Entity.EntityType.SCHEMA,
Entity.EntityType.TABLE,
@@ -66,7 +72,7 @@ public class RelationalEntityStoreIdResolver implements
EntityIdResolver {
if (ENTITY_TYPES_REQUIRING_METALAKE_ID.contains(type)) {
return getEntityIdsRequiringMetalakeId(nameIdentifier, type);
- } else if (ENTITY_TYPES_REQURING_CATALOG_IDS.contains(type)) {
+ } else if (ENTITY_TYPES_REQUIRING_CATALOG_IDS.contains(type)) {
CatalogIds catalogIds =
CatalogMetaService.getInstance()
.getCatalogIdByMetalakeAndCatalogName(
@@ -74,7 +80,7 @@ public class RelationalEntityStoreIdResolver implements
EntityIdResolver {
NameIdentifierUtil.getCatalogIdentifier(nameIdentifier).name());
return new NamespacedEntityId(catalogIds.getCatalogId(),
catalogIds.getMetalakeId());
- } else if (ENTITY_TYPES_REQURING_SCHEMA_IDS.contains(type)) {
+ } else if (ENTITY_TYPES_REQUIRING_SCHEMA_IDS.contains(type)) {
return getEntityIdsRequiringSchemaIds(nameIdentifier, type);
} else {
@@ -87,14 +93,14 @@ public class RelationalEntityStoreIdResolver implements
EntityIdResolver {
if (ENTITY_TYPES_REQUIRING_METALAKE_ID.contains(type)) {
return getEntityIdsRequiringMetalakeId(nameIdentifier, type).entityId();
- } else if (ENTITY_TYPES_REQURING_CATALOG_IDS.contains(type)) {
+ } else if (ENTITY_TYPES_REQUIRING_CATALOG_IDS.contains(type)) {
return CatalogMetaService.getInstance()
.getCatalogIdByMetalakeAndCatalogName(
NameIdentifierUtil.getMetalake(nameIdentifier),
NameIdentifierUtil.getCatalogIdentifier(nameIdentifier).name())
.getCatalogId();
- } else if (ENTITY_TYPES_REQURING_SCHEMA_IDS.contains(type)) {
+ } else if (ENTITY_TYPES_REQUIRING_SCHEMA_IDS.contains(type)) {
return getEntityIdsRequiringSchemaIds(nameIdentifier, type).entityId();
} else {
@@ -140,6 +146,18 @@ public class RelationalEntityStoreIdResolver implements
EntityIdResolver {
PolicyMetaService.getInstance()
.getPolicyIdByPolicyName(metalakeId, nameIdentifier.name());
return new NamespacedEntityId(policyId, metalakeId);
+
+ case JOB_TEMPLATE:
+ long jobTemplateId =
+ JobTemplateMetaService.getInstance()
+ .getJobTemplateIdByMetalakeIdAndName(metalakeId,
nameIdentifier.name());
+ return new NamespacedEntityId(jobTemplateId, metalakeId);
+
+ case JOB:
+ long jobId =
+
Long.parseLong(nameIdentifier.name().substring(JobHandle.JOB_ID_PREFIX.length()));
+ return new NamespacedEntityId(jobId, metalakeId);
+
default:
throw new IllegalArgumentException("Unsupported entity type: " + type);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
index 19af2cc44a..1ca511e755 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaMapper.java
@@ -70,4 +70,13 @@ public interface JobTemplateMetaMapper {
Integer updateJobTemplateMeta(
@Param("newJobTemplateMeta") JobTemplatePO newJobTemplatePO,
@Param("oldJobTemplateMeta") JobTemplatePO oldJobTemplatePO);
+
+ @SelectProvider(
+ type = JobTemplateMetaSQLProviderFactory.class,
+ method = "selectJobTemplateIdByMetalakeAndName")
+ Long selectJobTemplateIdByMetalakeAndName(
+ @Param("metalakeId") long metalakeId, @Param("jobTemplateName") String
jobTemplateName);
+
+ @SelectProvider(type = JobTemplateMetaSQLProviderFactory.class, method =
"selectJobTemplateById")
+ JobTemplatePO selectJobTemplateById(Long jobTemplateId);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
index 4dc6908e0d..a6b04e5947 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/JobTemplateMetaSQLProviderFactory.java
@@ -93,4 +93,13 @@ public class JobTemplateMetaSQLProviderFactory {
@Param("oldJobTemplateMeta") JobTemplatePO oldJobTemplatePO) {
return getProvider().updateJobTemplateMeta(newJobTemplatePO,
oldJobTemplatePO);
}
+
+ public static String selectJobTemplateIdByMetalakeAndName(
+ @Param("metalakeId") Long metalakeId, @Param("jobTemplateName") String
jobTemplateName) {
+ return getProvider().selectJobTemplateIdByMetalakeAndName(metalakeId,
jobTemplateName);
+ }
+
+ public static String selectJobTemplateById(@Param("jobTemplateId") Long
jobTemplateId) {
+ return getProvider().selectJobTemplateById(jobTemplateId);
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
index 3f6a1082b5..3c2c2e82c9 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/JobTemplateMetaBaseSQLProvider.java
@@ -141,4 +141,24 @@ public class JobTemplateMetaBaseSQLProvider {
+ " AND last_version = #{oldJobTemplateMeta.lastVersion}"
+ " AND deleted_at = 0";
}
+
+ public String selectJobTemplateIdByMetalakeAndName(
+ @Param("metalakeId") Long metalakeId, @Param("jobTemplateName") String
jobTemplateName) {
+ return "SELECT job_template_id FROM "
+ + JobTemplateMetaMapper.TABLE_NAME
+ + " WHERE deleted_at = 0 AND metalake_id = #{metalakeId} AND
job_template_name = #{jobTemplateName}";
+ }
+
+ public String selectJobTemplateById(@Param("jobTemplateId") Long
jobTemplateId) {
+ return "SELECT jtm.job_template_id AS jobTemplateId, jtm.job_template_name
AS jobTemplateName,"
+ + " jtm.metalake_id AS metalakeId, jtm.job_template_comment AS
jobTemplateComment,"
+ + " jtm.job_template_content AS jobTemplateContent, jtm.audit_info AS
auditInfo,"
+ + " jtm.current_version AS currentVersion, jtm.last_version AS
lastVersion,"
+ + " jtm.deleted_at AS deletedAt"
+ + " FROM "
+ + JobTemplateMetaMapper.TABLE_NAME
+ + " jtm"
+ + " WHERE jtm.job_template_id = #{jobTemplateId}"
+ + " AND jtm.deleted_at = 0";
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
index ad38114a70..cfaf51f2d6 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/JobTemplateMetaService.java
@@ -204,4 +204,19 @@ public class JobTemplateMetaService {
}
return jobTemplatePO;
}
+
+ public long getJobTemplateIdByMetalakeIdAndName(long metalakeId, String
name) {
+ Long jobTemplateId =
+ SessionUtils.getWithoutCommit(
+ JobTemplateMetaMapper.class,
+ mapper -> mapper.selectJobTemplateIdByMetalakeAndName(metalakeId,
name));
+
+ if (jobTemplateId == null) {
+ throw new NoSuchEntityException(
+ NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+ Entity.EntityType.JOB_TEMPLATE.name().toLowerCase(Locale.ROOT),
+ name);
+ }
+ return jobTemplateId;
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
index 75682d843f..e4b9425704 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/MetadataObjectService.java
@@ -23,6 +23,7 @@ import static
org.apache.gravitino.metrics.source.MetricsSource.GRAVITINO_RELATI
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,10 +32,12 @@ import java.util.stream.Collectors;
import org.apache.gravitino.Entity;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.MetadataObjects;
+import org.apache.gravitino.job.JobHandle;
import org.apache.gravitino.meta.GenericEntity;
import org.apache.gravitino.metrics.Monitored;
import org.apache.gravitino.storage.relational.mapper.CatalogMetaMapper;
import org.apache.gravitino.storage.relational.mapper.FilesetMetaMapper;
+import org.apache.gravitino.storage.relational.mapper.JobTemplateMetaMapper;
import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper;
import org.apache.gravitino.storage.relational.mapper.ModelMetaMapper;
import org.apache.gravitino.storage.relational.mapper.PolicyMetaMapper;
@@ -67,27 +70,22 @@ public class MetadataObjectService {
static final Map<MetadataObject.Type, Function<List<Long>, Map<Long,
String>>>
TYPE_TO_FULLNAME_FUNCTION_MAP =
- ImmutableMap.of(
- MetadataObject.Type.METALAKE,
- MetadataObjectService::getMetalakeObjectsFullName,
- MetadataObject.Type.CATALOG,
- MetadataObjectService::getCatalogObjectsFullName,
- MetadataObject.Type.SCHEMA,
- MetadataObjectService::getSchemaObjectsFullName,
- MetadataObject.Type.TABLE,
- MetadataObjectService::getTableObjectsFullName,
- MetadataObject.Type.FILESET,
- MetadataObjectService::getFilesetObjectsFullName,
- MetadataObject.Type.MODEL,
- MetadataObjectService::getModelObjectsFullName,
- MetadataObject.Type.TOPIC,
- MetadataObjectService::getTopicObjectsFullName,
- MetadataObject.Type.COLUMN,
- MetadataObjectService::getColumnObjectsFullName,
- MetadataObject.Type.TAG,
- MetadataObjectService::getTagObjectsFullName,
- MetadataObject.Type.POLICY,
- MetadataObjectService::getPolicyObjectsFullName);
+ ImmutableMap.<MetadataObject.Type, Function<List<Long>, Map<Long,
String>>>builder()
+ .put(MetadataObject.Type.METALAKE,
MetadataObjectService::getMetalakeObjectsFullName)
+ .put(MetadataObject.Type.CATALOG,
MetadataObjectService::getCatalogObjectsFullName)
+ .put(MetadataObject.Type.SCHEMA,
MetadataObjectService::getSchemaObjectsFullName)
+ .put(MetadataObject.Type.TABLE,
MetadataObjectService::getTableObjectsFullName)
+ .put(MetadataObject.Type.FILESET,
MetadataObjectService::getFilesetObjectsFullName)
+ .put(MetadataObject.Type.MODEL,
MetadataObjectService::getModelObjectsFullName)
+ .put(MetadataObject.Type.TOPIC,
MetadataObjectService::getTopicObjectsFullName)
+ .put(MetadataObject.Type.COLUMN,
MetadataObjectService::getColumnObjectsFullName)
+ .put(MetadataObject.Type.TAG,
MetadataObjectService::getTagObjectsFullName)
+ .put(MetadataObject.Type.POLICY,
MetadataObjectService::getPolicyObjectsFullName)
+ .put(MetadataObject.Type.JOB,
MetadataObjectService::getJobObjectsFullName)
+ .put(
+ MetadataObject.Type.JOB_TEMPLATE,
+ MetadataObjectService::getJobTemplateObjectsFullName)
+ .build();
private static Map<Long, String> getPolicyObjectsFullName(List<Long>
policyIds) {
if (policyIds == null || policyIds.isEmpty()) {
@@ -104,6 +102,33 @@ public class MetadataObjectService {
policyMetaMapper.selectPolicyByPolicyId(policyId).getPolicyName())));
}
+ private static Map<Long, String> getJobObjectsFullName(List<Long> jobIds) {
+ if (jobIds == null || jobIds.isEmpty()) {
+ return Maps.newHashMap();
+ }
+
+ return jobIds.stream()
+ .collect(Collectors.toMap(jobId -> jobId, jobId ->
JobHandle.JOB_ID_PREFIX + jobId));
+ }
+
+ private static Map<Long, String> getJobTemplateObjectsFullName(List<Long>
jobTemplateIds) {
+ if (jobTemplateIds == null || jobTemplateIds.isEmpty()) {
+ return Maps.newHashMap();
+ }
+
+ return jobTemplateIds.stream()
+ .collect(
+ Collectors.toMap(
+ jobTemplateId -> jobTemplateId,
+ jobTemplateId ->
+ SessionUtils.getWithoutCommit(
+ JobTemplateMetaMapper.class,
+ jobTemplateMetaMapper ->
+ jobTemplateMetaMapper
+ .selectJobTemplateById(jobTemplateId)
+ .jobTemplateName())));
+ }
+
private static Map<Long, String> getTagObjectsFullName(List<Long> tagIds) {
if (tagIds == null || tagIds.isEmpty()) {
return Map.of();
diff --git
a/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
b/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
index c33a0786a5..09f7ed271c 100644
--- a/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/MetadataObjectUtil.java
@@ -33,6 +33,8 @@ import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.exceptions.IllegalMetadataObjectException;
+import org.apache.gravitino.exceptions.NoSuchJobException;
+import org.apache.gravitino.exceptions.NoSuchJobTemplateException;
import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.exceptions.NoSuchRoleException;
@@ -55,6 +57,8 @@ public class MetadataObjectUtil {
.put(MetadataObject.Type.MODEL, Entity.EntityType.MODEL)
.put(MetadataObject.Type.TAG, Entity.EntityType.TAG)
.put(MetadataObject.Type.POLICY, Entity.EntityType.POLICY)
+ .put(MetadataObject.Type.JOB_TEMPLATE,
Entity.EntityType.JOB_TEMPLATE)
+ .put(MetadataObject.Type.JOB, Entity.EntityType.JOB)
.build();
private MetadataObjectUtil() {}
@@ -112,6 +116,10 @@ public class MetadataObjectUtil {
return NameIdentifierUtil.ofTag(metalakeName, metadataObject.name());
case POLICY:
return NameIdentifierUtil.ofPolicy(metalakeName,
metadataObject.name());
+ case JOB:
+ return NameIdentifierUtil.ofJob(metalakeName, metadataObject.name());
+ case JOB_TEMPLATE:
+ return NameIdentifierUtil.ofJobTemplate(metalakeName,
metadataObject.name());
case CATALOG:
case SCHEMA:
case TABLE:
@@ -196,21 +204,19 @@ public class MetadataObjectUtil {
try {
env.accessControlDispatcher().getRole(metalake, object.fullName());
} catch (NoSuchRoleException nsr) {
- Preconditions.checkArgument(
- exceptionToThrowSupplier != null, "exceptionToThrowSupplier
should not be null");
throw exceptionToThrowSupplier.get();
}
break;
+
case TAG:
NameIdentifierUtil.checkTag(identifier);
try {
env.tagDispatcher().getTag(metalake, object.fullName());
} catch (NoSuchTagException nsr) {
- Preconditions.checkArgument(
- exceptionToThrowSupplier != null, "exceptionToThrowSupplier
should not be null");
throw exceptionToThrowSupplier.get();
}
break;
+
case POLICY:
NameIdentifierUtil.checkPolicy(identifier);
try {
@@ -219,6 +225,25 @@ public class MetadataObjectUtil {
throw checkNotNull(exceptionToThrowSupplier).get();
}
break;
+
+ case JOB:
+ NameIdentifierUtil.checkJob(identifier);
+ try {
+ env.jobOperationDispatcher().getJob(metalake, object.fullName());
+ } catch (NoSuchJobException e) {
+ throw exceptionToThrowSupplier.get();
+ }
+ break;
+
+ case JOB_TEMPLATE:
+ NameIdentifierUtil.checkJobTemplate(identifier);
+ try {
+ env.jobOperationDispatcher().getJobTemplate(metalake,
object.fullName());
+ } catch (NoSuchJobTemplateException e) {
+ throw exceptionToThrowSupplier.get();
+ }
+ break;
+
default:
throw new IllegalArgumentException(
String.format("Doesn't support the type %s", object.type()));
diff --git
a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
index 73b23868c3..b607e3bfd5 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NameIdentifierUtil.java
@@ -517,6 +517,28 @@ public class NameIdentifierUtil {
NamespaceUtil.checkModelVersion(ident.namespace());
}
+ /**
+ * Check the given {@link NameIdentifier} is a job identifier. Throw an
{@link
+ * IllegalNameIdentifierException} if it's not.
+ *
+ * @param ident The job {@link NameIdentifier} to check.
+ */
+ public static void checkJob(NameIdentifier ident) {
+ NameIdentifier.check(ident != null, "Job identifier must not be null");
+ NamespaceUtil.checkJob(ident.namespace());
+ }
+
+ /**
+ * Check the given {@link NameIdentifier} is a job template identifier.
Throw an {@link
+ * IllegalNameIdentifierException} if it's not.
+ *
+ * @param ident The job template {@link NameIdentifier} to check.
+ */
+ public static void checkJobTemplate(NameIdentifier ident) {
+ NameIdentifier.check(ident != null, "Job template identifier must not be
null");
+ NamespaceUtil.checkJobTemplate(ident.namespace());
+ }
+
/**
* Convert the given {@link NameIdentifier} and {@link Entity.EntityType} to
{@link
* MetadataObject}.
@@ -578,9 +600,19 @@ public class NameIdentifierUtil {
case TAG:
checkTag(ident);
return MetadataObjects.of(null, ident.name(), MetadataObject.Type.TAG);
+
case POLICY:
checkPolicy(ident);
return MetadataObjects.of(null, ident.name(),
MetadataObject.Type.POLICY);
+
+ case JOB:
+ checkJob(ident);
+ return MetadataObjects.of(null, ident.name(), MetadataObject.Type.JOB);
+
+ case JOB_TEMPLATE:
+ checkJobTemplate(ident);
+ return MetadataObjects.of(null, ident.name(),
MetadataObject.Type.JOB_TEMPLATE);
+
default:
throw new IllegalArgumentException(
"Entity type " + entityType + " is not supported to convert to
MetadataObject");
diff --git a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
index c294a90145..1578c264fa 100644
--- a/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
+++ b/core/src/main/java/org/apache/gravitino/utils/NamespaceUtil.java
@@ -337,6 +337,30 @@ public class NamespaceUtil {
namespace);
}
+ /**
+ * Check if the given job template namespace is legal, throw an {@link
IllegalNamespaceException}
+ *
+ * @param namespace The job template namespace
+ */
+ public static void checkJobTemplate(Namespace namespace) {
+ check(
+ namespace != null && namespace.length() == 3,
+ "Job template namespace must be non-null and have 3 levels, the input
namespace is %s",
+ namespace);
+ }
+
+ /**
+ * Check if the given job namespace is legal, throw an {@link
IllegalNamespaceException}
+ *
+ * @param namespace The job namespace
+ */
+ public static void checkJob(Namespace namespace) {
+ check(
+ namespace != null && namespace.length() == 3,
+ "Job namespace must be non-null and have 3 levels, the input namespace
is %s",
+ namespace);
+ }
+
/**
* Check the given condition is true. Throw an {@link
IllegalNamespaceException} if it's not.
*
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 c563c45373..6f674d291a 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
@@ -38,6 +38,8 @@ import org.apache.gravitino.meta.CatalogEntity;
import org.apache.gravitino.meta.ColumnEntity;
import org.apache.gravitino.meta.FilesetEntity;
import org.apache.gravitino.meta.GroupEntity;
+import org.apache.gravitino.meta.JobEntity;
+import org.apache.gravitino.meta.JobTemplateEntity;
import org.apache.gravitino.meta.ModelEntity;
import org.apache.gravitino.meta.ModelVersionEntity;
import org.apache.gravitino.meta.RoleEntity;
@@ -76,6 +78,8 @@ public class MetadataIdConverter {
.put(Entity.EntityType.USER, UserEntity.class)
.put(Entity.EntityType.GROUP, GroupEntity.class)
.put(Entity.EntityType.ROLE, RoleEntity.class)
+ .put(Entity.EntityType.JOB_TEMPLATE, JobTemplateEntity.class)
+ .put(Entity.EntityType.JOB, JobEntity.class)
.build();
private MetadataIdConverter() {}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
index ee75d4f08b..325cecfe08 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/annotations/AuthorizationRequest.java
@@ -31,6 +31,7 @@ public @interface AuthorizationRequest {
enum RequestType {
COMMON,
ASSOCIATE_TAG,
- ASSOCIATE_POLICY
+ ASSOCIATE_POLICY,
+ RUN_JOB
}
}
diff --git
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
index 424e910f35..d4901eb1ba 100644
---
a/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
+++
b/server-common/src/main/java/org/apache/gravitino/server/authorization/expression/AuthorizationExpressionConverter.java
@@ -283,6 +283,11 @@ public class AuthorizationExpressionConverter {
"ANY_APPLY_POLICY",
"((ANY(APPLY_POLICY, METALAKE, POLICY))"
+ "&& !(ANY(DENY_APPLY_POLICY, METALAKE, POLICY)))");
+ expression =
+ expression.replaceAll(
+ "ANY_USE_JOB_TEMPLATE",
+ "((ANY(USE_JOB_TEMPLATE, METALAKE, JOB_TEMPLATE))"
+ + "&& !(ANY(DENY_USE_JOB_TEMPLATE, METALAKE, JOB_TEMPLATE)))");
expression =
expression.replaceAll(
CAN_SET_OWNER,
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
index 9d463c99a9..cefdba0b9a 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/GravitinoInterceptionService.java
@@ -51,6 +51,7 @@ import
org.apache.gravitino.server.web.filter.authorization.AuthorizeExecutorFac
import org.apache.gravitino.server.web.rest.CatalogOperations;
import org.apache.gravitino.server.web.rest.FilesetOperations;
import org.apache.gravitino.server.web.rest.GroupOperations;
+import org.apache.gravitino.server.web.rest.JobOperations;
import org.apache.gravitino.server.web.rest.MetadataObjectPolicyOperations;
import org.apache.gravitino.server.web.rest.MetadataObjectTagOperations;
import org.apache.gravitino.server.web.rest.MetalakeOperations;
@@ -101,7 +102,8 @@ public class GravitinoInterceptionService implements
InterceptionService {
MetadataObjectTagOperations.class.getName(),
TagOperations.class.getName(),
PolicyOperations.class.getName(),
- MetadataObjectPolicyOperations.class.getName()));
+ MetadataObjectPolicyOperations.class.getName(),
+ JobOperations.class.getName()));
}
@Override
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/ParameterUtil.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/ParameterUtil.java
index 068a9af7e5..ddefaf01fb 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/ParameterUtil.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/ParameterUtil.java
@@ -159,11 +159,26 @@ public class ParameterUtil {
Entity.EntityType.TAG,
NameIdentifierUtil.ofTag(metalake,
entities.get(Entity.EntityType.TAG)));
break;
+
case POLICY:
nameIdentifierMap.put(
Entity.EntityType.POLICY,
NameIdentifierUtil.ofPolicy(metalake,
entities.get(Entity.EntityType.POLICY)));
break;
+
+ case JOB:
+ nameIdentifierMap.put(
+ Entity.EntityType.JOB,
+ NameIdentifierUtil.ofJob(metalake,
entities.get(Entity.EntityType.JOB)));
+ break;
+
+ case JOB_TEMPLATE:
+ nameIdentifierMap.put(
+ Entity.EntityType.JOB_TEMPLATE,
+ NameIdentifierUtil.ofJobTemplate(
+ metalake, entities.get(Entity.EntityType.JOB_TEMPLATE)));
+ break;
+
default:
break;
}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
index 2fbe7a69db..b6051b1c88 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/AuthorizeExecutorFactory.java
@@ -54,6 +54,13 @@ public class AuthorizeExecutorFactory {
authorizationExpressionEvaluator,
pathParams,
entityType);
+ case RUN_JOB -> new RunJobAuthorizationExecutor(
+ parameters,
+ args,
+ metadataContext,
+ authorizationExpressionEvaluator,
+ pathParams,
+ entityType);
};
}
}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/RunJobAuthorizationExecutor.java
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/RunJobAuthorizationExecutor.java
new file mode 100644
index 0000000000..a4c5ea2039
--- /dev/null
+++
b/server/src/main/java/org/apache/gravitino/server/web/filter/authorization/RunJobAuthorizationExecutor.java
@@ -0,0 +1,78 @@
+/*
+ * 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.filter.authorization;
+
+import static
org.apache.gravitino.server.web.filter.ParameterUtil.extractFromParameters;
+
+import com.google.common.base.Preconditions;
+import java.lang.reflect.Parameter;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AuthorizationRequestContext;
+import org.apache.gravitino.dto.requests.JobRunRequest;
+import
org.apache.gravitino.server.authorization.expression.AuthorizationExpressionEvaluator;
+import org.apache.gravitino.utils.NameIdentifierUtil;
+
+public class RunJobAuthorizationExecutor implements AuthorizationExecutor {
+ private final Parameter[] parameters;
+ private final Object[] args;
+ private final Map<Entity.EntityType, NameIdentifier> metadataContext;
+ private final AuthorizationExpressionEvaluator
authorizationExpressionEvaluator;
+ private final Map<String, Object> pathParams;
+ private final String entityType;
+
+ public RunJobAuthorizationExecutor(
+ Parameter[] parameters,
+ Object[] args,
+ Map<Entity.EntityType, NameIdentifier> metadataContext,
+ AuthorizationExpressionEvaluator authorizationExpressionEvaluator,
+ Map<String, Object> pathParams,
+ String entityType) {
+ this.parameters = parameters;
+ this.args = args;
+ this.metadataContext = metadataContext;
+ this.authorizationExpressionEvaluator = authorizationExpressionEvaluator;
+ this.pathParams = pathParams;
+ this.entityType = entityType;
+ }
+
+ @Override
+ public boolean execute() throws Exception {
+ Object request = extractFromParameters(parameters, args);
+ if (request == null) {
+ return false;
+ }
+
+ AuthorizationRequestContext context = new AuthorizationRequestContext();
+ Preconditions.checkArgument(
+ request instanceof JobRunRequest,
+ "Expected JobRunRequest but found %s",
+ request.getClass().getSimpleName());
+ JobRunRequest jobRunRequest = (JobRunRequest) request;
+ jobRunRequest.validate();
+ String jobTemplateName = jobRunRequest.getJobTemplateName();
+ NameIdentifier metalake = metadataContext.get(Entity.EntityType.METALAKE);
+ NameIdentifier jobTemplateNameIdentifier =
+ NameIdentifierUtil.ofJobTemplate(metalake.name(), jobTemplateName);
+ metadataContext.put(Entity.EntityType.JOB_TEMPLATE,
jobTemplateNameIdentifier);
+
+ return authorizationExpressionEvaluator.evaluate(
+ metadataContext, pathParams, context, Optional.ofNullable(entityType));
+ }
+}
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
index ca5bc551e6..c29b14f9f3 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/JobOperations.java
@@ -21,6 +21,7 @@ package org.apache.gravitino.server.web.rest;
import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Lists;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
@@ -40,6 +41,7 @@ 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.GravitinoEnv;
import org.apache.gravitino.dto.job.JobDTO;
import org.apache.gravitino.dto.job.JobTemplateDTO;
@@ -63,7 +65,12 @@ import org.apache.gravitino.meta.AuditInfo;
import org.apache.gravitino.meta.JobEntity;
import org.apache.gravitino.meta.JobTemplateEntity;
import org.apache.gravitino.metrics.MetricNames;
+import org.apache.gravitino.server.authorization.MetadataAuthzHelper;
+import
org.apache.gravitino.server.authorization.annotations.AuthorizationExpression;
+import
org.apache.gravitino.server.authorization.annotations.AuthorizationMetadata;
+import
org.apache.gravitino.server.authorization.annotations.AuthorizationRequest;
import org.apache.gravitino.server.web.Utils;
+import org.apache.gravitino.utils.NameIdentifierUtil;
import org.apache.gravitino.utils.NamespaceUtil;
import org.apache.gravitino.utils.PrincipalUtils;
import org.slf4j.Logger;
@@ -88,8 +95,10 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "list-job-templates." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "list-job-templates", absolute = true)
+ @AuthorizationExpression(expression = "")
public Response listJobTemplates(
- @PathParam("metalake") String metalake,
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
@QueryParam("details") @DefaultValue("false") boolean details) {
LOG.info(
"Received request to list job templates in metalake: {}, details: {}",
metalake, details);
@@ -98,8 +107,19 @@ public class JobOperations {
return Utils.doAs(
httpRequest,
() -> {
- List<JobTemplateDTO> jobTemplates =
-
toJobTemplateDTOs(jobOperationDispatcher.listJobTemplates(metalake));
+ List<JobTemplateEntity> jobTemplateEntities =
+ Lists.newArrayList(
+ MetadataAuthzHelper.filterByExpression(
+ metalake,
+ "METALAKE::OWNER || JOB_TEMPLATE::OWNER ||
ANY_USE_JOB_TEMPLATE",
+ Entity.EntityType.JOB_TEMPLATE,
+ jobOperationDispatcher
+ .listJobTemplates(metalake)
+ .toArray(new JobTemplateEntity[0]),
+ jobTemplateEntity ->
+ NameIdentifierUtil.ofJobTemplate(metalake,
jobTemplateEntity.name())));
+ List<JobTemplateDTO> jobTemplates =
toJobTemplateDTOs(jobTemplateEntities);
+
if (details) {
LOG.info("List {} job templates in metalake: {}",
jobTemplates.size(), metalake);
return Utils.ok(new JobTemplateListResponse(jobTemplates));
@@ -124,8 +144,11 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "register-job-template." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "register-job-template", absolute = true)
+ @AuthorizationExpression(expression = "METALAKE::OWNER ||
METALAKE::REGISTER_JOB_TEMPLATE")
public Response registerJobTemplate(
- @PathParam("metalake") String metalake, JobTemplateRegisterRequest
request) {
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ JobTemplateRegisterRequest request) {
LOG.info(
"Received request to register job template {} in metalake: {}",
request.getJobTemplate().name(),
@@ -158,8 +181,13 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "get-job-template." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "get-job-template", absolute = true)
+ @AuthorizationExpression(
+ expression = "METALAKE::OWNER || JOB_TEMPLATE::OWNER ||
ANY_USE_JOB_TEMPLATE")
public Response getJobTemplate(
- @PathParam("metalake") String metalake, @PathParam("name") String name) {
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @PathParam("name") @AuthorizationMetadata(type =
Entity.EntityType.JOB_TEMPLATE)
+ String name) {
LOG.info("Received request to get job template: {} in metalake: {}", name,
metalake);
try {
@@ -183,8 +211,12 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "delete-job-template." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "delete-job-template", absolute = true)
+ @AuthorizationExpression(expression = "METALAKE::OWNER ||
JOB_TEMPLATE::OWNER")
public Response deleteJobTemplate(
- @PathParam("metalake") String metalake, @PathParam("name") String name) {
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @PathParam("name") @AuthorizationMetadata(type =
Entity.EntityType.JOB_TEMPLATE)
+ String name) {
LOG.info("Received request to delete job template: {} in metalake: {}",
name, metalake);
try {
@@ -211,9 +243,12 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "alter-job-template." + MetricNames.HTTP_PROCESS_DURATION,
absolute = true)
@ResponseMetered(name = "alter-job-template", absolute = true)
+ @AuthorizationExpression(expression = "METALAKE::OWNER ||
JOB_TEMPLATE::OWNER")
public Response alterJobTemplate(
- @PathParam("metalake") String metalake,
- @PathParam("name") String jobTemplateName,
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @PathParam("name") @AuthorizationMetadata(type =
Entity.EntityType.JOB_TEMPLATE)
+ String jobTemplateName,
JobTemplateUpdatesRequest request) {
LOG.info(
"Received request to alter job template: {} in metalake: {}",
jobTemplateName, metalake);
@@ -245,8 +280,10 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "list-jobs." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
@ResponseMetered(name = "list-jobs", absolute = true)
+ @AuthorizationExpression(expression = "")
public Response listJobs(
- @PathParam("metalake") String metalake,
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
@QueryParam("jobTemplateName") String jobTemplateName) {
LOG.info(
"Received request to list jobs in metalake {}{}",
@@ -258,7 +295,15 @@ public class JobOperations {
httpRequest,
() -> {
List<JobEntity> jobEntities =
- jobOperationDispatcher.listJobs(metalake,
Optional.ofNullable(jobTemplateName));
+ Lists.newArrayList(
+ MetadataAuthzHelper.filterByExpression(
+ metalake,
+ "METALAKE::OWNER || JOB::OWNER",
+ Entity.EntityType.JOB,
+ jobOperationDispatcher
+ .listJobs(metalake,
Optional.ofNullable(jobTemplateName))
+ .toArray(new JobEntity[0]),
+ jobEntity -> NameIdentifierUtil.ofJob(metalake,
jobEntity.name())));
List<JobDTO> jobDTOs = toJobDTOs(jobEntities);
LOG.info("Listed {} jobs in metalake {}", jobEntities.size(),
metalake);
@@ -275,7 +320,11 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "get-job." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
@ResponseMetered(name = "get-job", absolute = true)
- public Response getJob(@PathParam("metalake") String metalake,
@PathParam("jobId") String jobId) {
+ @AuthorizationExpression(expression = "METALAKE::OWNER || JOB::OWNER")
+ public Response getJob(
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @PathParam("jobId") @AuthorizationMetadata(type = Entity.EntityType.JOB)
String jobId) {
LOG.info("Received request to get job {} in metalake {}", jobId, metalake);
try {
@@ -297,7 +346,14 @@ public class JobOperations {
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "run-job." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
@ResponseMetered(name = "run-job", absolute = true)
- public Response runJob(@PathParam("metalake") String metalake, JobRunRequest
request) {
+ @AuthorizationExpression(
+ expression =
+ "METALAKE::OWNER || (METALAKE::RUN_JOB && (ANY_USE_JOB_TEMPLATE ||
JOB_TEMPLATE::OWNER))")
+ public Response runJob(
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @AuthorizationRequest(type = AuthorizationRequest.RequestType.RUN_JOB)
+ JobRunRequest request) {
LOG.info(
"Received request to run job {} in metalake: {}",
request.getJobTemplateName(), metalake);
@@ -329,8 +385,11 @@ public class JobOperations {
@Path("runs/{jobId}")
@Produces("application/vnd.gravitino.v1+json")
@Timed(name = "cancel-job." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
+ @AuthorizationExpression(expression = "METALAKE::OWNER || JOB::OWNER")
public Response cancelJob(
- @PathParam("metalake") String metalake, @PathParam("jobId") String
jobId) {
+ @PathParam("metalake") @AuthorizationMetadata(type =
Entity.EntityType.METALAKE)
+ String metalake,
+ @PathParam("jobId") @AuthorizationMetadata(type = Entity.EntityType.JOB)
String jobId) {
LOG.info("Received request to cancel job {} in metalake {}", jobId,
metalake);
try {