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 f052358ad7 [#9071] feat(authz): Add job and job template privileges 
interfaces and documents (#9177)
f052358ad7 is described below

commit f052358ad76b8f37f6f30052179eea397ed2b247
Author: roryqi <[email protected]>
AuthorDate: Tue Nov 25 21:54:52 2025 +0800

    [#9071] feat(authz): Add job and job template privileges interfaces and 
documents (#9177)
    
    ### What changes were proposed in this pull request?
    
    Add job and job template privileges interfaces and documents
    
    ### Why are the changes needed?
    
    Fix: #9071
    
    ### Does this PR introduce _any_ user-facing change?
    
    Added the documents.
    
    ### How was this patch tested?
    
    Added unit tests.
---
 .../java/org/apache/gravitino/MetadataObject.java  |   5 +-
 .../java/org/apache/gravitino/MetadataObjects.java |  83 ++++++++------
 .../apache/gravitino/authorization/Privilege.java  |   7 +-
 .../apache/gravitino/authorization/Privileges.java | 125 ++++++++++++++++++++-
 .../gravitino/authorization/SecurableObjects.java  |  34 ++++++
 .../org/apache/gravitino/TestMetadataObjects.java  |  28 +++++
 .../authorization/TestSecurableObjects.java        |  49 +++++++-
 .../java/org/apache/gravitino/tag/TagManager.java  |  13 ++-
 docs/security/access-control.md                    |  27 ++++-
 9 files changed, 325 insertions(+), 46 deletions(-)

diff --git a/api/src/main/java/org/apache/gravitino/MetadataObject.java 
b/api/src/main/java/org/apache/gravitino/MetadataObject.java
index 4df271d049..d773205065 100644
--- a/api/src/main/java/org/apache/gravitino/MetadataObject.java
+++ b/api/src/main/java/org/apache/gravitino/MetadataObject.java
@@ -68,7 +68,10 @@ public interface MetadataObject {
      * A policy can be associated with a metadata object for data governance 
and similar purposes.
      */
     POLICY,
-    ;
+    /** A job represents a data processing task in Gravitino. */
+    JOB,
+    /** A job template represents a reusable template for creating jobs in 
Gravitino. */
+    JOB_TEMPLATE;
   }
 
   /**
diff --git a/api/src/main/java/org/apache/gravitino/MetadataObjects.java 
b/api/src/main/java/org/apache/gravitino/MetadataObjects.java
index c3f7e8a7c3..586e1785b4 100644
--- a/api/src/main/java/org/apache/gravitino/MetadataObjects.java
+++ b/api/src/main/java/org/apache/gravitino/MetadataObjects.java
@@ -21,8 +21,12 @@ package org.apache.gravitino;
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import javax.annotation.Nullable;
 import org.apache.commons.lang3.StringUtils;
 
@@ -36,6 +40,36 @@ public class MetadataObjects {
 
   private static final Joiner DOT_JOINER = Joiner.on('.');
 
+  private static final Set<MetadataObject.Type> VALID_SINGLE_LEVEL_NAME_TYPES =
+      Sets.newHashSet(
+          MetadataObject.Type.CATALOG,
+          MetadataObject.Type.METALAKE,
+          MetadataObject.Type.ROLE,
+          MetadataObject.Type.TAG,
+          MetadataObject.Type.JOB,
+          MetadataObject.Type.JOB_TEMPLATE,
+          MetadataObject.Type.POLICY);
+
+  private static final Set<MetadataObject.Type> VALID_TWO_LEVEL_NAME_TYPES =
+      Sets.newHashSet(MetadataObject.Type.SCHEMA);
+
+  private static final Set<MetadataObject.Type> VALID_THREE_LEVEL_NAME_TYPES =
+      Sets.newHashSet(
+          MetadataObject.Type.FILESET,
+          MetadataObject.Type.TABLE,
+          MetadataObject.Type.TOPIC,
+          MetadataObject.Type.MODEL);
+
+  private static final Set<MetadataObject.Type> VALID_FOUR_LEVEL_NAME_TYPES =
+      Sets.newHashSet(MetadataObject.Type.COLUMN);
+
+  private static final Map<Set<MetadataObject.Type>, Integer> 
TYPE_TO_EXPECT_LENGTH =
+      ImmutableMap.of(
+          VALID_SINGLE_LEVEL_NAME_TYPES, 1,
+          VALID_TWO_LEVEL_NAME_TYPES, 2,
+          VALID_THREE_LEVEL_NAME_TYPES, 3,
+          VALID_FOUR_LEVEL_NAME_TYPES, 4);
+
   private MetadataObjects() {}
 
   /**
@@ -49,6 +83,10 @@ public class MetadataObjects {
   public static MetadataObject of(String parent, String name, 
MetadataObject.Type type) {
     Preconditions.checkArgument(name != null, "Cannot create a metadata object 
with null name");
     Preconditions.checkArgument(type != null, "Cannot create a metadata object 
with no type");
+    if (VALID_SINGLE_LEVEL_NAME_TYPES.contains(type)) {
+      Preconditions.checkArgument(
+          parent == null, "If the type is " + type + ", parent must be null");
+    }
 
     String fullName = parent == null ? name : DOT_JOINER.join(parent, name);
     return parse(fullName, type);
@@ -64,35 +102,19 @@ public class MetadataObjects {
   public static MetadataObject of(List<String> names, MetadataObject.Type 
type) {
     Preconditions.checkArgument(names != null, "Cannot create a metadata 
object with null names");
     Preconditions.checkArgument(!names.isEmpty(), "Cannot create a metadata 
object with no names");
-    Preconditions.checkArgument(
-        names.size() <= 4,
-        "Cannot create a metadata object with the name length which is greater 
than 4");
     Preconditions.checkArgument(type != null, "Cannot create a metadata object 
with no type");
 
-    Preconditions.checkArgument(
-        names.size() != 1
-            || type == MetadataObject.Type.CATALOG
-            || type == MetadataObject.Type.METALAKE
-            || type == MetadataObject.Type.ROLE
-            || type == MetadataObject.Type.TAG
-            || type == MetadataObject.Type.POLICY,
-        "If the length of names is 1, it must be the CATALOG, METALAKE,TAG, or 
ROLE type");
+    Integer expectedLength =
+        TYPE_TO_EXPECT_LENGTH.entrySet().stream()
+            .filter(entry -> entry.getKey().contains(type))
+            .map(Map.Entry::getValue)
+            .findFirst()
+            .orElseThrow(
+                () -> new IllegalArgumentException("Unsupported metadata 
object type: " + type));
 
     Preconditions.checkArgument(
-        names.size() != 2 || type == MetadataObject.Type.SCHEMA,
-        "If the length of names is 2, it must be the SCHEMA type");
-
-    Preconditions.checkArgument(
-        names.size() != 3
-            || type == MetadataObject.Type.FILESET
-            || type == MetadataObject.Type.TABLE
-            || type == MetadataObject.Type.TOPIC
-            || type == MetadataObject.Type.MODEL,
-        "If the length of names is 3, it must be FILESET, TABLE, TOPIC or 
MODEL");
-
-    Preconditions.checkArgument(
-        names.size() != 4 || type == MetadataObject.Type.COLUMN,
-        "If the length of names is 4, it must be COLUMN");
+        names.size() == expectedLength,
+        "If the type is " + type + ", the length of names must be " + 
expectedLength);
 
     for (String name : names) {
       checkName(name);
@@ -114,9 +136,7 @@ public class MetadataObjects {
     }
 
     // Return null if the object is the root object
-    if (object.type() == MetadataObject.Type.METALAKE
-        || object.type() == MetadataObject.Type.CATALOG
-        || object.type() == MetadataObject.Type.ROLE) {
+    if (VALID_SINGLE_LEVEL_NAME_TYPES.contains(object.type())) {
       return null;
     }
 
@@ -155,11 +175,8 @@ public class MetadataObjects {
         StringUtils.isNotBlank(fullName), "Metadata object full name cannot be 
blank");
 
     List<String> parts = DOT_SPLITTER.splitToList(fullName);
-    if (type == MetadataObject.Type.ROLE) {
-      return MetadataObjects.of(Collections.singletonList(fullName), 
MetadataObject.Type.ROLE);
-    }
-    if (type == MetadataObject.Type.TAG) {
-      return MetadataObjects.of(Collections.singletonList(fullName), 
MetadataObject.Type.TAG);
+    if (VALID_SINGLE_LEVEL_NAME_TYPES.contains(type)) {
+      return of(Collections.singletonList(fullName), type);
     }
 
     return MetadataObjects.of(parts, type);
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java 
b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
index 23a7c4a2cc..332ff3be58 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privilege.java
@@ -103,7 +103,12 @@ public interface Privilege {
     CREATE_POLICY(0L, 1L << 23),
     /** The privilege to apply a policy */
     APPLY_POLICY(0L, 1L << 24),
-    ;
+    /** The privilege to register a job template */
+    REGISTER_JOB_TEMPLATE(0L, 1L << 25),
+    /** The privilege to use a job template */
+    USE_JOB_TEMPLATE(0L, 1L << 26),
+    /** The privilege to run a job */
+    RUN_JOB(0L, 1L << 27);
 
     private final long highBits;
     private final long lowBits;
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java 
b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
index 11697521a0..b499069a7d 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/Privileges.java
@@ -133,14 +133,29 @@ public class Privileges {
         return CreateModelVersion.allow();
       case USE_MODEL:
         return UseModel.allow();
+
+        // Tag
       case CREATE_TAG:
         return CreateTag.allow();
       case APPLY_TAG:
         return ApplyTag.allow();
+
+        // Policy
       case APPLY_POLICY:
         return ApplyPolicy.allow();
       case CREATE_POLICY:
         return CreatePolicy.allow();
+
+        // Job template
+      case REGISTER_JOB_TEMPLATE:
+        return RegisterJobTemplate.allow();
+      case USE_JOB_TEMPLATE:
+        return UseJobTemplate.allow();
+
+        // Job
+      case RUN_JOB:
+        return RunJob.allow();
+
       default:
         throw new IllegalArgumentException("Doesn't support the privilege: " + 
name);
     }
@@ -222,10 +237,28 @@ public class Privileges {
         return CreateModelVersion.deny();
       case USE_MODEL:
         return UseModel.deny();
+
+        // Tag
       case CREATE_TAG:
         return CreateTag.deny();
       case APPLY_TAG:
         return ApplyTag.deny();
+
+        // Policy
+      case APPLY_POLICY:
+        return ApplyPolicy.deny();
+      case CREATE_POLICY:
+        return CreatePolicy.deny();
+
+        // Job template
+      case REGISTER_JOB_TEMPLATE:
+        return RegisterJobTemplate.deny();
+      case USE_JOB_TEMPLATE:
+        return UseJobTemplate.deny();
+
+        // Job
+      case RUN_JOB:
+        return RunJob.deny();
       default:
         throw new IllegalArgumentException("Doesn't support the privilege: " + 
name);
     }
@@ -984,7 +1017,6 @@ public class Privileges {
     protected CreatePolicy(Condition condition, Name name) {
       super(condition, name);
     }
-
     /**
      * @return The instance with allow condition of the privilege.
      */
@@ -1005,6 +1037,35 @@ public class Privileges {
     }
   }
 
+  /** The privilege to run a job. */
+  public static class RunJob extends GenericPrivilege<RunJob> {
+    private static final RunJob ALLOW_INSTANCE = new RunJob(Condition.ALLOW, 
Name.RUN_JOB);
+    private static final RunJob DENY_INSTANCE = new RunJob(Condition.DENY, 
Name.RUN_JOB);
+
+    private RunJob(Condition condition, Name name) {
+      super(condition, name);
+    }
+
+    /**
+     * @return The instance with allow condition of the privilege.
+     */
+    public static RunJob allow() {
+      return ALLOW_INSTANCE;
+    }
+
+    /**
+     * @return The instance with deny condition of the privilege.
+     */
+    public static RunJob deny() {
+      return DENY_INSTANCE;
+    }
+
+    @Override
+    public boolean canBindTo(MetadataObject.Type type) {
+      return type == MetadataObject.Type.METALAKE;
+    }
+  }
+
   /** The privilege to apply policy to object. */
   public static final class ApplyPolicy extends GenericPrivilege<ApplyPolicy> {
 
@@ -1042,4 +1103,66 @@ public class Privileges {
       return type == MetadataObject.Type.METALAKE || type == 
MetadataObject.Type.POLICY;
     }
   }
+
+  /** The privilege to register a job template. */
+  public static class RegisterJobTemplate extends 
GenericPrivilege<RegisterJobTemplate> {
+    private static final RegisterJobTemplate ALLOW_INSTANCE =
+        new RegisterJobTemplate(Condition.ALLOW, Name.REGISTER_JOB_TEMPLATE);
+    private static final RegisterJobTemplate DENY_INSTANCE =
+        new RegisterJobTemplate(Condition.DENY, Name.REGISTER_JOB_TEMPLATE);
+
+    private RegisterJobTemplate(Condition condition, Name name) {
+      super(condition, name);
+    }
+
+    /**
+     * @return The instance with allow condition of the privilege.
+     */
+    public static RegisterJobTemplate allow() {
+      return ALLOW_INSTANCE;
+    }
+
+    /**
+     * @return The instance with deny condition of the privilege.
+     */
+    public static RegisterJobTemplate deny() {
+      return DENY_INSTANCE;
+    }
+
+    @Override
+    public boolean canBindTo(MetadataObject.Type type) {
+      return type == MetadataObject.Type.METALAKE;
+    }
+  }
+
+  /** The privilege to use a job template. */
+  public static class UseJobTemplate extends GenericPrivilege<UseJobTemplate> {
+    private static final UseJobTemplate ALLOW_INSTANCE =
+        new UseJobTemplate(Condition.ALLOW, Name.USE_JOB_TEMPLATE);
+    private static final UseJobTemplate DENY_INSTANCE =
+        new UseJobTemplate(Condition.DENY, Name.USE_JOB_TEMPLATE);
+
+    private UseJobTemplate(Condition condition, Name name) {
+      super(condition, name);
+    }
+
+    /**
+     * @return The instance with allow condition of the privilege.
+     */
+    public static UseJobTemplate allow() {
+      return ALLOW_INSTANCE;
+    }
+
+    /**
+     * @return The instance with deny condition of the privilege.
+     */
+    public static UseJobTemplate deny() {
+      return DENY_INSTANCE;
+    }
+
+    @Override
+    public boolean canBindTo(MetadataObject.Type type) {
+      return type == MetadataObject.Type.METALAKE || type == 
MetadataObject.Type.JOB_TEMPLATE;
+    }
+  }
 }
diff --git 
a/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java 
b/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
index e63e3d0982..e6f444cbb9 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/SecurableObjects.java
@@ -138,6 +138,40 @@ public class SecurableObjects {
     return of(MetadataObject.Type.MODEL, names, privileges);
   }
 
+  /**
+   * Create the tag {@link SecurableObject} with the given tag name and 
privileges.
+   *
+   * @param tag The tag name
+   * @param privileges The privileges of the tag
+   * @return The created tag {@link SecurableObject}
+   */
+  public static SecurableObject ofTag(String tag, List<Privilege> privileges) {
+    return of(MetadataObject.Type.TAG, Lists.newArrayList(tag), privileges);
+  }
+
+  /**
+   * Create the policy {@link SecurableObject} with the given policy name and 
privileges.
+   *
+   * @param policy The policy name
+   * @param privileges The privileges of the policy
+   * @return The created policy {@link SecurableObject}
+   */
+  public static SecurableObject ofPolicy(String policy, List<Privilege> 
privileges) {
+    return of(MetadataObject.Type.POLICY, Lists.newArrayList(policy), 
privileges);
+  }
+
+  /**
+   * Create the job template {@link SecurableObject} with the given job 
template name and
+   * privileges.
+   *
+   * @param jobTemplate The job template name
+   * @param privileges The privileges of the job template
+   * @return The created job template {@link SecurableObject}
+   */
+  public static SecurableObject ofJobTemplate(String jobTemplate, 
List<Privilege> privileges) {
+    return of(MetadataObject.Type.JOB_TEMPLATE, 
Lists.newArrayList(jobTemplate), privileges);
+  }
+
   private static class SecurableObjectImpl extends MetadataObjectImpl 
implements SecurableObject {
 
     private List<Privilege> privileges;
diff --git a/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java 
b/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
index f792220e18..391a150eba 100644
--- a/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
+++ b/api/src/test/java/org/apache/gravitino/TestMetadataObjects.java
@@ -99,4 +99,32 @@ public class TestMetadataObjects {
     MetadataObject roleObject3 = MetadataObjects.parse("role", 
MetadataObject.Type.ROLE);
     Assertions.assertEquals("role", roleObject3.fullName());
   }
+
+  @Test
+  public void testJobObject() {
+    MetadataObject jobObject = MetadataObjects.of(null, "job_12345", 
MetadataObject.Type.JOB);
+    Assertions.assertEquals("job_12345", jobObject.fullName());
+
+    MetadataObject jobObject2 = MetadataObjects.parse("job_12345", 
MetadataObject.Type.JOB);
+    Assertions.assertEquals("job_12345", jobObject2.fullName());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> MetadataObjects.of("parent", "job_12345", 
MetadataObject.Type.JOB));
+  }
+
+  @Test
+  public void testJobTemplateObject() {
+    MetadataObject jobTemplateObject =
+        MetadataObjects.of(null, "template_abc", 
MetadataObject.Type.JOB_TEMPLATE);
+    Assertions.assertEquals("template_abc", jobTemplateObject.fullName());
+
+    MetadataObject jobTemplateObject2 =
+        MetadataObjects.parse("template_abc", 
MetadataObject.Type.JOB_TEMPLATE);
+    Assertions.assertEquals("template_abc", jobTemplateObject2.fullName());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> MetadataObjects.of("parent", "template_abc", 
MetadataObject.Type.JOB_TEMPLATE));
+  }
 }
diff --git 
a/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
 
b/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
index 813ac9f4b2..b05b52a326 100644
--- 
a/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
+++ 
b/api/src/test/java/org/apache/gravitino/authorization/TestSecurableObjects.java
@@ -108,7 +108,7 @@ public class TestSecurableObjects {
                     MetadataObject.Type.METALAKE,
                     Lists.newArrayList("metalake", "catalog"),
                     Lists.newArrayList(Privileges.UseCatalog.allow())));
-    Assertions.assertTrue(e.getMessage().contains("length of names is 2"));
+    Assertions.assertTrue(e.getMessage().contains("the length of names must be 
1"));
     e =
         Assertions.assertThrows(
             IllegalArgumentException.class,
@@ -117,7 +117,7 @@ public class TestSecurableObjects {
                     MetadataObject.Type.CATALOG,
                     Lists.newArrayList("metalake", "catalog"),
                     Lists.newArrayList(Privileges.UseCatalog.allow())));
-    Assertions.assertTrue(e.getMessage().contains("length of names is 2"));
+    Assertions.assertTrue(e.getMessage().contains("the length of names must be 
1"));
 
     e =
         Assertions.assertThrows(
@@ -127,7 +127,7 @@ public class TestSecurableObjects {
                     MetadataObject.Type.TABLE,
                     Lists.newArrayList("metalake"),
                     Lists.newArrayList(Privileges.SelectTable.allow())));
-    Assertions.assertTrue(e.getMessage().contains("the length of names is 1"));
+    Assertions.assertTrue(e.getMessage().contains("the length of names must be 
3"));
     e =
         Assertions.assertThrows(
             IllegalArgumentException.class,
@@ -136,7 +136,7 @@ public class TestSecurableObjects {
                     MetadataObject.Type.TOPIC,
                     Lists.newArrayList("metalake"),
                     Lists.newArrayList(Privileges.ConsumeTopic.allow())));
-    Assertions.assertTrue(e.getMessage().contains("the length of names is 1"));
+    Assertions.assertTrue(e.getMessage().contains("the length of names must be 
3"));
     e =
         Assertions.assertThrows(
             IllegalArgumentException.class,
@@ -145,7 +145,7 @@ public class TestSecurableObjects {
                     MetadataObject.Type.FILESET,
                     Lists.newArrayList("metalake"),
                     Lists.newArrayList(Privileges.ReadFileset.allow())));
-    Assertions.assertTrue(e.getMessage().contains("the length of names is 1"));
+    Assertions.assertTrue(e.getMessage().contains("the length of names must be 
3"));
 
     e =
         Assertions.assertThrows(
@@ -155,7 +155,7 @@ public class TestSecurableObjects {
                     MetadataObject.Type.SCHEMA,
                     Lists.newArrayList("catalog", "schema", "table"),
                     Lists.newArrayList(Privileges.UseSchema.allow())));
-    Assertions.assertTrue(e.getMessage().contains("the length of names is 3"));
+    Assertions.assertTrue(e.getMessage().contains("the length of names must be 
2"));
   }
 
   @Test
@@ -184,6 +184,9 @@ public class TestSecurableObjects {
     Privilege applyTag = Privileges.ApplyTag.allow();
     Privilege createPolicy = Privileges.CreatePolicy.allow();
     Privilege applyPolicy = Privileges.ApplyPolicy.allow();
+    Privilege registerJobTemplate = Privileges.RegisterJobTemplate.allow();
+    Privilege runJob = Privileges.RunJob.allow();
+    Privilege useJobTemplate = Privileges.UseJobTemplate.allow();
 
     // Test create catalog
     
Assertions.assertTrue(createCatalog.canBindTo(MetadataObject.Type.METALAKE));
@@ -396,6 +399,7 @@ public class TestSecurableObjects {
     Assertions.assertFalse(applyTag.canBindTo(MetadataObject.Type.SCHEMA));
     Assertions.assertFalse(applyTag.canBindTo(MetadataObject.Type.TABLE));
     Assertions.assertFalse(applyTag.canBindTo(MetadataObject.Type.MODEL));
+
     Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.TOPIC));
     Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.FILESET));
     Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.ROLE));
@@ -419,5 +423,38 @@ public class TestSecurableObjects {
     Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.TOPIC));
     Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.FILESET));
     Assertions.assertFalse(useModel.canBindTo(MetadataObject.Type.ROLE));
+
+    
Assertions.assertTrue(registerJobTemplate.canBindTo(MetadataObject.Type.METALAKE));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.CATALOG));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.SCHEMA));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.TABLE));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.TOPIC));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.FILESET));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.ROLE));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.COLUMN));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.JOB_TEMPLATE));
+    
Assertions.assertFalse(registerJobTemplate.canBindTo(MetadataObject.Type.JOB));
+
+    Assertions.assertTrue(runJob.canBindTo(MetadataObject.Type.METALAKE));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.CATALOG));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.SCHEMA));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.TABLE));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.TOPIC));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.FILESET));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.ROLE));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.COLUMN));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.JOB));
+    Assertions.assertFalse(runJob.canBindTo(MetadataObject.Type.JOB_TEMPLATE));
+
+    
Assertions.assertTrue(useJobTemplate.canBindTo(MetadataObject.Type.METALAKE));
+    
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.CATALOG));
+    
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.SCHEMA));
+    
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.TABLE));
+    
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.TOPIC));
+    
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.FILESET));
+    Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.ROLE));
+    
Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.COLUMN));
+    
Assertions.assertTrue(useJobTemplate.canBindTo(MetadataObject.Type.JOB_TEMPLATE));
+    Assertions.assertFalse(useJobTemplate.canBindTo(MetadataObject.Type.JOB));
   }
 }
diff --git a/core/src/main/java/org/apache/gravitino/tag/TagManager.java 
b/core/src/main/java/org/apache/gravitino/tag/TagManager.java
index 867e49d1c1..38c3f5bab7 100644
--- a/core/src/main/java/org/apache/gravitino/tag/TagManager.java
+++ b/core/src/main/java/org/apache/gravitino/tag/TagManager.java
@@ -64,6 +64,16 @@ public class TagManager implements TagDispatcher {
 
   private final EntityStore entityStore;
 
+  private static final Set<MetadataObject.Type> 
SUPPORTED_METADATA_OBJECT_TYPES_FOR_TAGS =
+      Sets.newHashSet(
+          MetadataObject.Type.CATALOG,
+          MetadataObject.Type.SCHEMA,
+          MetadataObject.Type.TABLE,
+          MetadataObject.Type.FILESET,
+          MetadataObject.Type.TOPIC,
+          MetadataObject.Type.COLUMN,
+          MetadataObject.Type.MODEL);
+
   public TagManager(IdGenerator idGenerator, EntityStore entityStore) {
     this.idGenerator = idGenerator;
     this.entityStore = entityStore;
@@ -298,8 +308,7 @@ public class TagManager implements TagDispatcher {
       String metalake, MetadataObject metadataObject, String[] tagsToAdd, 
String[] tagsToRemove)
       throws NoSuchMetadataObjectException, TagAlreadyAssociatedException {
     Preconditions.checkArgument(
-        !metadataObject.type().equals(MetadataObject.Type.METALAKE)
-            && !metadataObject.type().equals(MetadataObject.Type.ROLE),
+        
SUPPORTED_METADATA_OBJECT_TYPES_FOR_TAGS.contains(metadataObject.type()),
         "Cannot associate tags for unsupported metadata object type %s",
         metadataObject.type());
 
diff --git a/docs/security/access-control.md b/docs/security/access-control.md
index 23d1a0c8f1..9c4a5c0328 100644
--- a/docs/security/access-control.md
+++ b/docs/security/access-control.md
@@ -115,7 +115,7 @@ If a securable object needs to be managed by more than one 
person at the same ti
 The metadata object that supports ownership is as follows:
 
 | Metadata Object Type |
-| -------------------- |
+|----------------------|
 | Metalake             |
 | Catalog              |
 | Schema               |
@@ -125,6 +125,8 @@ The metadata object that supports ownership is as follows:
 | Role                 |
 | Model                |
 | Tag                  |
+| JobTemplate          |
+| Job                  |
 
 ### User
 Users are generally granted one or multiple Roles, and users have different 
operating privileges depending on their Role.
@@ -279,6 +281,19 @@ DENY `WRITE_FILESET` won‘t deny the `READ_FILESET` 
operation if the user has t
 | CREATE_POLICY | Metalake                  | Create a policy                  
         |
 | APPLY_POLICY  | Metalake, Policy          | Associate policies with metadata 
objects. |
 
+### Job template privileges
+
+| Name                  | Supports Securable Object | Operation                
               |
+|-----------------------|---------------------------|-----------------------------------------|
+| REGISTER_JOB_TEMPLATE | Metalake                  | Register a job template  
               |
+| USE_JOB_TEMPLATE      | Metalake, JobTemplate     | Use a job template when 
running the job |
+
+### Job privileges
+| Name    | Supports Securable Object | Operation |
+|---------|---------------------------|-----------|
+| RUN_JOB | Metalake                  | Run a job |
+
+
 ## Inheritance Model
 
 Securable objects in Gravitino are hierarchical and privileges are inherited 
downward.
@@ -1059,4 +1074,12 @@ The following table lists the required privileges for 
each API.
 | associate-object-policies         | Requires both `APPLY_POLICY` permission 
and permission to **load metadata objects**.                                    
                                                                                
                                      |
 | list policies for object          | Requires both permission to **get the 
policy** and permission to **load metadata objects**.                           
                                                                                
                                        |
 | get policy for object             | Requires both permission to **get the 
policy** and permission to **load metadata objects**.                           
                                                                                
                                        |
-
+| list job templates                | The owner of the metalake can see all 
the job templates, others can see the job templates which they can get.         
                                                                                
                                        |
+| register a job template           | `REGISTER_JOB_TEMPLATE` on the metalake 
or the owner of the metalake.                                                   
                                                                                
                                      |
+| get a job template                | `USE_JOB_TEMPLATE` on the metalake or 
job template, the owner of the metalake or the job template.                    
                                                                                
                                        |
+| delete a job template             | The owner of the metalake or the job 
template.                                                                       
                                                                                
                                         |
+| alter a job template              | The owner of the metalake or the job 
template.                                                                       
                                                                                
                                         |
+| list jobs                         | The owner of the metalake can see all 
the jobs, others can see the jobs which they can get.                           
                                                                                
                                        |                                       
                                                                                
                                                                                
                     
+| run a job                         | The owner of the metalake , or have both 
`RUN_JOB` on the metalake and `USE_JOB_TEMPLATE` on the job template            
                                                                                
                                     |
+| get a job                         | The owner of the metalake or the job.    
                                                                                
                                                                                
                                     |
+| cancel a job                      | The owner of the metalake or the job.    
                                                                                
                                                                                
                                     |

Reply via email to