This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-1.3 by this push:
new 0b5808de89 [Cherry-pick to branch-1.3] [#13859] fix(authz): expose
inaccessible object tag and policy associations (#13889)
0b5808de89 is described below
commit 0b5808de8993be4e9a41e45b12776d06090b59e6
Author: roryqi <[email protected]>
AuthorDate: Fri Oct 16 09:05:27 2026 +0800
[Cherry-pick to branch-1.3] [#13859] fix(authz): expose inaccessible object
tag and policy associations (#13889)
### What changes were proposed in this pull request?
Backport #13863 to branch-1.3.
- Add `hasInaccessibleTags` and `hasInaccessiblePolicies` to
metadata-object name and detail list responses.
- Add backward-compatible Java visibility APIs using `TagList` and
`PolicyList`, plus the equivalent Python tag APIs.
- Preserve branch-1.3's direct policy-association APIs while applying
authorization filtering consistently to direct and inherited
associations.
- Add SDK, authorization, response DTO, OpenAPI, and user-guide
coverage.
### Why are the changes needed?
When all associated tags or policies are inaccessible, an empty list is
indistinguishable from an object with no associations. The new
indicators report that associations were omitted without exposing their
names, counts, details, or inheritance sources.
Fix: #13859
### Does this PR introduce _any_ user-facing change?
Yes. Tag and policy list responses expose `hasInaccessibleTags` and
`hasInaccessiblePolicies`. Java and Python clients add
backward-compatible APIs that return visible items together with these
indicators.
### How was this patch tested?
- `./gradlew :api:build -x :api:test
:clients:client-java:compileTestJava`
- `./gradlew :clients:client-java:test --tests
org.apache.gravitino.client.TestSupportTags --tests
org.apache.gravitino.client.TestSupportPolicies`
- `python3 -m unittest tests.unittests.test_tag_api` (29 tests passed)
- `./gradlew :docs:build`
- Spotless checks
- Authorization Docker ITs were attempted, but both stopped during Hive
container initialization because the mapped ports timed out before test
execution.
---
.../org/apache/gravitino/policy/PolicyList.java | 61 ++++++++++++
.../apache/gravitino/policy/SupportsPolicies.java | 27 ++++++
.../org/apache/gravitino/tag/SupportsTags.java | 26 +++++
.../java/org/apache/gravitino/tag/TagList.java | 61 ++++++++++++
.../apache/gravitino/client/BaseSchemaCatalog.java | 22 +++++
.../org/apache/gravitino/client/GenericColumn.java | 11 +++
.../apache/gravitino/client/GenericFileset.java | 22 +++++
.../apache/gravitino/client/GenericFunction.java | 11 +++
.../org/apache/gravitino/client/GenericModel.java | 22 +++++
.../org/apache/gravitino/client/GenericSchema.java | 22 +++++
.../org/apache/gravitino/client/GenericTopic.java | 22 +++++
.../org/apache/gravitino/client/GenericView.java | 11 +++
.../client/MetadataObjectPolicyOperations.java | 32 ++++--
.../client/MetadataObjectTagOperations.java | 32 ++++--
.../apache/gravitino/client/RelationalTable.java | 22 +++++
.../gravitino/client/TestSupportPolicies.java | 55 ++++++++---
.../apache/gravitino/client/TestSupportTags.java | 21 ++--
.../test/authorization/PolicyAuthorizationIT.java | 77 +++++----------
.../TagOperationsAuthorizationIT.java | 108 ++++++---------------
.../client-python/gravitino/api/tag/__init__.py | 2 +
.../gravitino/api/tag/supports_tags.py | 17 ++++
.../gravitino/api/tag/{__init__.py => tag_list.py} | 24 +++--
.../gravitino/client/base_schema_catalog.py | 7 ++
.../gravitino/client/generic_column.py | 7 ++
.../gravitino/client/generic_fileset.py | 7 ++
.../gravitino/client/generic_function.py | 9 ++
.../gravitino/client/generic_model.py | 8 +-
.../gravitino/client/generic_schema.py | 7 ++
.../client-python/gravitino/client/generic_view.py | 7 ++
.../client/metadata_object_tag_operations.py | 30 ++++--
.../gravitino/client/relational_table.py | 7 ++
.../gravitino/dto/responses/tag_response.py | 13 ++-
clients/client-python/tests/unittests/mock_base.py | 4 +-
.../client-python/tests/unittests/test_tag_api.py | 82 +++++++++++++++-
docs/manage-policies-in-gravitino.md | 37 ++++++-
docs/manage-tags-in-gravitino.md | 47 ++++++++-
36 files changed, 779 insertions(+), 201 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/policy/PolicyList.java
b/api/src/main/java/org/apache/gravitino/policy/PolicyList.java
new file mode 100644
index 0000000000..11281e61b7
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/policy/PolicyList.java
@@ -0,0 +1,61 @@
+/*
+ * 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.policy;
+
+import com.google.common.base.Preconditions;
+import org.apache.gravitino.annotation.Evolving;
+
+/**
+ * A list of visible policies and whether authorization filtering omitted
other policies.
+ *
+ * @param <T> The type of the returned policy names or policy objects.
+ */
+@Evolving
+public final class PolicyList<T> {
+
+ private final T[] policies;
+ private final boolean hasInaccessiblePolicies;
+
+ /**
+ * Creates a policy list.
+ *
+ * @param policies The visible policy names or policy objects.
+ * @param hasInaccessiblePolicies Whether one or more inaccessible policies
were omitted.
+ */
+ public PolicyList(T[] policies, boolean hasInaccessiblePolicies) {
+ Preconditions.checkArgument(policies != null, "Policies must not be null");
+ this.policies = policies.clone();
+ this.hasInaccessiblePolicies = hasInaccessiblePolicies;
+ }
+
+ /**
+ * @return The visible policy names or policy objects.
+ */
+ public T[] policies() {
+ return policies.clone();
+ }
+
+ /**
+ * @return True if one or more inaccessible policies were omitted, false
otherwise.
+ */
+ public boolean hasInaccessiblePolicies() {
+ return hasInaccessiblePolicies;
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/policy/SupportsPolicies.java
b/api/src/main/java/org/apache/gravitino/policy/SupportsPolicies.java
index 65356fd34b..79e43756c4 100644
--- a/api/src/main/java/org/apache/gravitino/policy/SupportsPolicies.java
+++ b/api/src/main/java/org/apache/gravitino/policy/SupportsPolicies.java
@@ -33,11 +33,38 @@ public interface SupportsPolicies {
*/
String[] listPolicies();
+ /**
+ * List the visible policy names and whether authorization filtering omitted
other policies.
+ *
+ * <p>The default implementation preserves compatibility with existing
implementations but cannot
+ * detect omitted policies. Implementations that support authorization
filtering should override
+ * this method.
+ *
+ * @return The visible policy names and authorization-filtering status.
+ */
+ default PolicyList<String> listPoliciesWithVisibility() {
+ return new PolicyList<>(listPolicies(), false);
+ }
+
/**
* @return List all the policies with details for the specific object.
*/
Policy[] listPolicyInfos();
+ /**
+ * List the visible policies with details and whether authorization
filtering omitted other
+ * policies.
+ *
+ * <p>The default implementation preserves compatibility with existing
implementations but cannot
+ * detect omitted policies. Implementations that support authorization
filtering should override
+ * this method.
+ *
+ * @return The visible policies and authorization-filtering status.
+ */
+ default PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return new PolicyList<>(listPolicyInfos(), false);
+ }
+
/**
* Get a policy by its name for the specific object.
*
diff --git a/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
b/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
index 0a909c4c8c..ed4f33d0de 100644
--- a/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
+++ b/api/src/main/java/org/apache/gravitino/tag/SupportsTags.java
@@ -37,6 +37,19 @@ public interface SupportsTags {
*/
String[] listTags();
+ /**
+ * List the visible tag names and whether authorization filtering omitted
other tags.
+ *
+ * <p>The default implementation preserves compatibility with existing
implementations but cannot
+ * detect omitted tags. Implementations that support authorization filtering
should override this
+ * method.
+ *
+ * @return The visible tag names and authorization-filtering status.
+ */
+ default TagList<String> listTagsWithVisibility() {
+ return new TagList<>(listTags(), false);
+ }
+
/**
* List all the tags with details for the specific object.
*
@@ -44,6 +57,19 @@ public interface SupportsTags {
*/
Tag[] listTagsInfo();
+ /**
+ * List the visible tags with details and whether authorization filtering
omitted other tags.
+ *
+ * <p>The default implementation preserves compatibility with existing
implementations but cannot
+ * detect omitted tags. Implementations that support authorization filtering
should override this
+ * method.
+ *
+ * @return The visible tags and authorization-filtering status.
+ */
+ default TagList<Tag> listTagsInfoWithVisibility() {
+ return new TagList<>(listTagsInfo(), false);
+ }
+
/**
* Get a tag by its name for the specific object.
*
diff --git a/api/src/main/java/org/apache/gravitino/tag/TagList.java
b/api/src/main/java/org/apache/gravitino/tag/TagList.java
new file mode 100644
index 0000000000..1eea86680f
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/tag/TagList.java
@@ -0,0 +1,61 @@
+/*
+ * 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.tag;
+
+import com.google.common.base.Preconditions;
+import org.apache.gravitino.annotation.Evolving;
+
+/**
+ * A list of visible tags and whether authorization filtering omitted other
tags.
+ *
+ * @param <T> The type of the returned tag names or tag objects.
+ */
+@Evolving
+public final class TagList<T> {
+
+ private final T[] tags;
+ private final boolean hasInaccessibleTags;
+
+ /**
+ * Creates a tag list.
+ *
+ * @param tags The visible tag names or tag objects.
+ * @param hasInaccessibleTags Whether one or more inaccessible tags were
omitted.
+ */
+ public TagList(T[] tags, boolean hasInaccessibleTags) {
+ Preconditions.checkArgument(tags != null, "Tags must not be null");
+ this.tags = tags.clone();
+ this.hasInaccessibleTags = hasInaccessibleTags;
+ }
+
+ /**
+ * @return The visible tag names or tag objects.
+ */
+ public T[] tags() {
+ return tags.clone();
+ }
+
+ /**
+ * @return True if one or more inaccessible tags were omitted, false
otherwise.
+ */
+ public boolean hasInaccessibleTags() {
+ return hasInaccessibleTags;
+ }
+}
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
index 0553abfc4b..c8a3d2be52 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/BaseSchemaCatalog.java
@@ -54,10 +54,12 @@ import org.apache.gravitino.function.FunctionChange;
import org.apache.gravitino.function.FunctionDefinition;
import org.apache.gravitino.function.FunctionType;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rest.RESTUtils;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/**
* BaseSchemaCatalog is the base abstract class for all the catalog with
schema. It provides the
@@ -273,11 +275,21 @@ abstract class BaseSchemaCatalog extends CatalogDTO
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) {
return objectTagOperations.getTag(name);
@@ -293,11 +305,21 @@ abstract class BaseSchemaCatalog extends CatalogDTO
return objectPolicyOperations.listPolicies();
}
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ return objectPolicyOperations.listPoliciesWithVisibility();
+ }
+
@Override
public Policy[] listPolicyInfos() {
return objectPolicyOperations.listPolicyInfos();
}
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return objectPolicyOperations.listPolicyInfosWithVisibility();
+ }
+
@Override
public Policy getPolicy(String name) throws NoSuchPolicyException {
return objectPolicyOperations.getPolicy(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericColumn.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericColumn.java
index aacf022e95..40d26c1fc1 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericColumn.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericColumn.java
@@ -29,6 +29,7 @@ import org.apache.gravitino.rel.expressions.Expression;
import org.apache.gravitino.rel.types.Type;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic column. */
public class GenericColumn implements Column, SupportsTags {
@@ -62,11 +63,21 @@ public class GenericColumn implements Column, SupportsTags {
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
index a6963a6a44..a73413ef3e 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFileset.java
@@ -35,9 +35,11 @@ import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.file.Fileset;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic fileset. */
class GenericFileset
@@ -121,11 +123,21 @@ class GenericFileset
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
@@ -141,11 +153,21 @@ class GenericFileset
return objectPolicyOperations.listPolicies();
}
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ return objectPolicyOperations.listPoliciesWithVisibility();
+ }
+
@Override
public Policy[] listPolicyInfos() {
return objectPolicyOperations.listPolicyInfos();
}
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return objectPolicyOperations.listPolicyInfosWithVisibility();
+ }
+
@Override
public Policy getPolicy(String name) throws NoSuchPolicyException {
return objectPolicyOperations.getPolicy(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFunction.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFunction.java
index fd3bcb459b..1a5738b07f 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFunction.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericFunction.java
@@ -32,6 +32,7 @@ import org.apache.gravitino.function.FunctionDefinition;
import org.apache.gravitino.function.FunctionType;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic function. */
class GenericFunction implements Function, SupportsTags {
@@ -90,11 +91,21 @@ class GenericFunction implements Function, SupportsTags {
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModel.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModel.java
index 076ed38143..f5149040b0 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModel.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericModel.java
@@ -33,9 +33,11 @@ import
org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.exceptions.TagAlreadyAssociatedException;
import org.apache.gravitino.model.Model;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic model. */
class GenericModel implements Model, SupportsTags, SupportsPolicies {
@@ -119,11 +121,21 @@ class GenericModel implements Model, SupportsTags,
SupportsPolicies {
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
@@ -140,11 +152,21 @@ class GenericModel implements Model, SupportsTags,
SupportsPolicies {
return objectPolicyOperations.listPolicies();
}
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ return objectPolicyOperations.listPoliciesWithVisibility();
+ }
+
@Override
public Policy[] listPolicyInfos() {
return objectPolicyOperations.listPolicyInfos();
}
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return objectPolicyOperations.listPolicyInfosWithVisibility();
+ }
+
@Override
public Policy getPolicy(String name) throws NoSuchPolicyException {
return objectPolicyOperations.getPolicy(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
index bbb5f84300..d8a0f8c67c 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericSchema.java
@@ -30,9 +30,11 @@ import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic schema. */
class GenericSchema implements Schema, SupportsTags, SupportsRoles,
SupportsPolicies {
@@ -94,11 +96,21 @@ class GenericSchema implements Schema, SupportsTags,
SupportsRoles, SupportsPoli
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
@@ -114,11 +126,21 @@ class GenericSchema implements Schema, SupportsTags,
SupportsRoles, SupportsPoli
return objectPolicyOperations.listPolicies();
}
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ return objectPolicyOperations.listPoliciesWithVisibility();
+ }
+
@Override
public Policy[] listPolicyInfos() {
return objectPolicyOperations.listPolicyInfos();
}
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return objectPolicyOperations.listPolicyInfosWithVisibility();
+ }
+
@Override
public Policy getPolicy(String name) throws NoSuchPolicyException {
return objectPolicyOperations.getPolicy(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
index a040bbbd93..67419a40b4 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericTopic.java
@@ -32,9 +32,11 @@ import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.messaging.Topic;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic topic. */
class GenericTopic implements Topic, SupportsTags, SupportsRoles,
SupportsPolicies {
@@ -98,11 +100,21 @@ class GenericTopic implements Topic, SupportsTags,
SupportsRoles, SupportsPolici
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
@@ -118,11 +130,21 @@ class GenericTopic implements Topic, SupportsTags,
SupportsRoles, SupportsPolici
return objectPolicyOperations.listPolicies();
}
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ return objectPolicyOperations.listPoliciesWithVisibility();
+ }
+
@Override
public Policy[] listPolicyInfos() {
return objectPolicyOperations.listPolicyInfos();
}
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return objectPolicyOperations.listPolicyInfosWithVisibility();
+ }
+
@Override
public Policy getPolicy(String name) throws NoSuchPolicyException {
return objectPolicyOperations.getPolicy(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericView.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericView.java
index a38228806a..ba7851d6e5 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GenericView.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GenericView.java
@@ -33,6 +33,7 @@ import org.apache.gravitino.rel.Representation;
import org.apache.gravitino.rel.View;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a generic view. */
class GenericView implements View, SupportsTags {
@@ -100,11 +101,21 @@ class GenericView implements View, SupportsTags {
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectPolicyOperations.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectPolicyOperations.java
index 820bd890eb..512dc92333 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectPolicyOperations.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectPolicyOperations.java
@@ -26,11 +26,13 @@ import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.dto.requests.PoliciesAssociateRequest;
+import org.apache.gravitino.dto.responses.MetadataObjectPolicyListResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
-import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyNameListResponse;
import org.apache.gravitino.dto.responses.PolicyResponse;
import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rest.RESTUtils;
@@ -60,31 +62,43 @@ class MetadataObjectPolicyOperations implements
SupportsPolicies {
@Override
public String[] listPolicies() {
- NameListResponse resp =
+ return listPoliciesWithVisibility().policies();
+ }
+
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ PolicyNameListResponse resp =
restClient.get(
policyRequestPath,
- NameListResponse.class,
+ PolicyNameListResponse.class,
Collections.emptyMap(),
ErrorHandlers.policyErrorHandler());
resp.validate();
- return resp.getNames();
+ return new PolicyList<>(resp.getNames(), resp.hasInaccessiblePolicies());
}
@Override
public Policy[] listPolicyInfos() {
- PolicyListResponse resp =
+ return listPolicyInfosWithVisibility().policies();
+ }
+
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ MetadataObjectPolicyListResponse resp =
restClient.get(
policyRequestPath,
ImmutableMap.of("details", "true"),
- PolicyListResponse.class,
+ MetadataObjectPolicyListResponse.class,
Collections.emptyMap(),
ErrorHandlers.policyErrorHandler());
resp.validate();
- return Arrays.stream(resp.getPolicies())
- .map(policyDTO -> new GenericPolicy(policyDTO, restClient,
metalakeName))
- .toArray(Policy[]::new);
+ Policy[] policies =
+ Arrays.stream(resp.getPolicies())
+ .map(policyDTO -> new GenericPolicy(policyDTO, restClient,
metalakeName))
+ .toArray(Policy[]::new);
+ return new PolicyList<>(policies, resp.hasInaccessiblePolicies());
}
@Override
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
index 29ec00512b..914bbacfa2 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/MetadataObjectTagOperations.java
@@ -26,13 +26,15 @@ import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.dto.requests.TagsAssociateRequest;
+import org.apache.gravitino.dto.responses.MetadataObjectTagListResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
-import org.apache.gravitino.dto.responses.TagListResponse;
+import org.apache.gravitino.dto.responses.TagNameListResponse;
import org.apache.gravitino.dto.responses.TagResponse;
import org.apache.gravitino.exceptions.NoSuchTagException;
import org.apache.gravitino.rest.RESTUtils;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/**
* The implementation of {@link SupportsTags}. This interface will be
composited into catalog,
@@ -60,31 +62,43 @@ class MetadataObjectTagOperations implements SupportsTags {
@Override
public String[] listTags() {
- NameListResponse resp =
+ return listTagsWithVisibility().tags();
+ }
+
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ TagNameListResponse resp =
restClient.get(
tagRequestPath,
- NameListResponse.class,
+ TagNameListResponse.class,
Collections.emptyMap(),
ErrorHandlers.tagErrorHandler());
resp.validate();
- return resp.getNames();
+ return new TagList<>(resp.getNames(), resp.hasInaccessibleTags());
}
@Override
public Tag[] listTagsInfo() {
- TagListResponse resp =
+ return listTagsInfoWithVisibility().tags();
+ }
+
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ MetadataObjectTagListResponse resp =
restClient.get(
tagRequestPath,
ImmutableMap.of("details", "true"),
- TagListResponse.class,
+ MetadataObjectTagListResponse.class,
Collections.emptyMap(),
ErrorHandlers.tagErrorHandler());
resp.validate();
- return Arrays.stream(resp.getTags())
- .map(tagDTO -> new GenericTag(tagDTO, restClient, metalakeName))
- .toArray(Tag[]::new);
+ Tag[] tags =
+ Arrays.stream(resp.getTags())
+ .map(tagDTO -> new GenericTag(tagDTO, restClient, metalakeName))
+ .toArray(Tag[]::new);
+ return new TagList<>(tags, resp.hasInaccessibleTags());
}
@Override
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
index fac38e76ac..a0d1f122bf 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/RelationalTable.java
@@ -49,6 +49,7 @@ import
org.apache.gravitino.exceptions.PartitionAlreadyExistsException;
import org.apache.gravitino.exceptions.PolicyAlreadyAssociatedException;
import org.apache.gravitino.exceptions.UnmodifiableStatisticException;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.SupportsPartitions;
@@ -69,6 +70,7 @@ import org.apache.gravitino.stats.SupportsPartitionStatistics;
import org.apache.gravitino.stats.SupportsStatistics;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
/** Represents a relational table. */
class RelationalTable
@@ -377,11 +379,21 @@ class RelationalTable
return objectTagOperations.listTags();
}
+ @Override
+ public TagList<String> listTagsWithVisibility() {
+ return objectTagOperations.listTagsWithVisibility();
+ }
+
@Override
public Tag[] listTagsInfo() {
return objectTagOperations.listTagsInfo();
}
+ @Override
+ public TagList<Tag> listTagsInfoWithVisibility() {
+ return objectTagOperations.listTagsInfoWithVisibility();
+ }
+
@Override
public Tag getTag(String name) throws NoSuchTagException {
return objectTagOperations.getTag(name);
@@ -397,11 +409,21 @@ class RelationalTable
return objectPolicyOperations.listPolicies();
}
+ @Override
+ public PolicyList<String> listPoliciesWithVisibility() {
+ return objectPolicyOperations.listPoliciesWithVisibility();
+ }
+
@Override
public Policy[] listPolicyInfos() {
return objectPolicyOperations.listPolicyInfos();
}
+ @Override
+ public PolicyList<Policy> listPolicyInfosWithVisibility() {
+ return objectPolicyOperations.listPolicyInfosWithVisibility();
+ }
+
@Override
public Policy getPolicy(String name) throws NoSuchPolicyException {
return objectPolicyOperations.getPolicy(name);
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportPolicies.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportPolicies.java
index da58d21c65..720a16a364 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportPolicies.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportPolicies.java
@@ -27,6 +27,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import java.util.Locale;
+import java.util.Optional;
import java.util.Set;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.MetadataObject;
@@ -43,14 +44,16 @@ import org.apache.gravitino.dto.rel.ColumnDTO;
import org.apache.gravitino.dto.rel.TableDTO;
import org.apache.gravitino.dto.requests.PoliciesAssociateRequest;
import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.MetadataObjectPolicyListResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
-import org.apache.gravitino.dto.responses.PolicyListResponse;
+import org.apache.gravitino.dto.responses.PolicyNameListResponse;
import org.apache.gravitino.dto.responses.PolicyResponse;
import org.apache.gravitino.exceptions.NoSuchPolicyException;
import org.apache.gravitino.exceptions.NotFoundException;
import org.apache.gravitino.file.Fileset;
import org.apache.gravitino.messaging.Topic;
import org.apache.gravitino.policy.Policy;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rel.Table;
import org.apache.gravitino.rel.types.Types;
@@ -361,14 +364,15 @@ public class TestSupportPolicies extends TestBase {
+ "/policies";
String[] policies = new String[] {"policy1", "policy2"};
- NameListResponse resp = new NameListResponse(policies);
+ PolicyNameListResponse resp = new PolicyNameListResponse(policies, true);
buildMockResource(Method.GET, path, null, resp, SC_OK);
- String[] actualPolicies = supportsPolicies.listPolicies();
- Assertions.assertArrayEquals(policies, actualPolicies);
+ PolicyList<String> policyList =
supportsPolicies.listPoliciesWithVisibility();
+ Assertions.assertArrayEquals(policies, policyList.policies());
+ Assertions.assertTrue(policyList.hasInaccessiblePolicies());
// Return empty list
- NameListResponse resp1 = new NameListResponse(new String[0]);
+ PolicyNameListResponse resp1 = new PolicyNameListResponse(new String[0],
false);
buildMockResource(Method.GET, path, null, resp1, SC_OK);
String[] actualPolicies1 = supportsPolicies.listPolicies();
@@ -407,6 +411,7 @@ public class TestSupportPolicies extends TestBase {
.withName("policy1")
.withPolicyType("custom")
.withContent(contentDTO)
+ .withInherited(Optional.of(false))
.withAudit(AuditDTO.builder().withCreator("test").build())
.build();
PolicyDTO policy2 =
@@ -414,20 +419,28 @@ public class TestSupportPolicies extends TestBase {
.withName("policy2")
.withPolicyType("custom")
.withContent(contentDTO)
+ .withInherited(Optional.of(true))
.withAudit(AuditDTO.builder().withCreator("test").build())
.build();
PolicyDTO[] policies = new PolicyDTO[] {policy1, policy2};
- PolicyListResponse resp = new PolicyListResponse(policies);
- buildMockResource(Method.GET, path, null, resp, SC_OK);
+ MetadataObjectPolicyListResponse resp = new
MetadataObjectPolicyListResponse(policies, true);
+ buildMockResource(
+ Method.GET, path, Collections.singletonMap("details", "true"), null,
resp, SC_OK);
- Policy[] actualPolicies = supportsPolicies.listPolicyInfos();
+ PolicyList<Policy> policyList =
supportsPolicies.listPolicyInfosWithVisibility();
+ Policy[] actualPolicies = policyList.policies();
Assertions.assertEquals(2, actualPolicies.length);
Assertions.assertEquals("policy1", actualPolicies[0].name());
Assertions.assertEquals("policy2", actualPolicies[1].name());
+ Assertions.assertFalse(actualPolicies[0].inherited().orElseThrow());
+ Assertions.assertTrue(actualPolicies[1].inherited().orElseThrow());
+ Assertions.assertTrue(policyList.hasInaccessiblePolicies());
// Return empty list
- PolicyListResponse resp1 = new PolicyListResponse(new PolicyDTO[0]);
- buildMockResource(Method.GET, path, null, resp1, SC_OK);
+ MetadataObjectPolicyListResponse resp1 =
+ new MetadataObjectPolicyListResponse(new PolicyDTO[0], false);
+ buildMockResource(
+ Method.GET, path, Collections.singletonMap("details", "true"), null,
resp1, SC_OK);
Policy[] actualPolicies1 = supportsPolicies.listPolicyInfos();
Assertions.assertArrayEquals(new Policy[0], actualPolicies1);
@@ -435,16 +448,30 @@ public class TestSupportPolicies extends TestBase {
// Test throw NotFoundException
ErrorResponse errorResp =
ErrorResponse.notFound(NotFoundException.class.getSimpleName(), "mock
error");
- buildMockResource(Method.GET, path, null, errorResp, SC_NOT_FOUND);
+ buildMockResource(
+ Method.GET,
+ path,
+ Collections.singletonMap("details", "true"),
+ null,
+ errorResp,
+ SC_NOT_FOUND);
- Throwable ex = Assertions.assertThrows(NotFoundException.class,
supportsPolicies::listPolicies);
+ Throwable ex =
+ Assertions.assertThrows(NotFoundException.class,
supportsPolicies::listPolicyInfos);
Assertions.assertTrue(ex.getMessage().contains("mock error"));
// Test throw internal error
ErrorResponse errorResp1 = ErrorResponse.internalError("mock error");
- buildMockResource(Method.GET, path, null, errorResp1,
SC_INTERNAL_SERVER_ERROR);
+ buildMockResource(
+ Method.GET,
+ path,
+ Collections.singletonMap("details", "true"),
+ null,
+ errorResp1,
+ SC_INTERNAL_SERVER_ERROR);
- Throwable ex1 = Assertions.assertThrows(RuntimeException.class,
supportsPolicies::listPolicies);
+ Throwable ex1 =
+ Assertions.assertThrows(RuntimeException.class,
supportsPolicies::listPolicyInfos);
Assertions.assertTrue(ex1.getMessage().contains("mock error"));
}
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
index 7ee356ebae..09b204b226 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestSupportTags.java
@@ -44,8 +44,9 @@ import org.apache.gravitino.dto.rel.TableDTO;
import org.apache.gravitino.dto.rel.ViewDTO;
import org.apache.gravitino.dto.requests.TagsAssociateRequest;
import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.MetadataObjectTagListResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
-import org.apache.gravitino.dto.responses.TagListResponse;
+import org.apache.gravitino.dto.responses.TagNameListResponse;
import org.apache.gravitino.dto.responses.TagResponse;
import org.apache.gravitino.dto.tag.TagDTO;
import org.apache.gravitino.exceptions.NoSuchTagException;
@@ -61,6 +62,7 @@ import org.apache.gravitino.rel.View;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.Tag;
+import org.apache.gravitino.tag.TagList;
import org.apache.hc.core5.http.Method;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@@ -496,14 +498,15 @@ public class TestSupportTags extends TestBase {
+ "/tags";
String[] tags = new String[] {"tag1", "tag2"};
- NameListResponse resp = new NameListResponse(tags);
+ TagNameListResponse resp = new TagNameListResponse(tags, true);
buildMockResource(Method.GET, path, null, resp, SC_OK);
- String[] actualTags = supportsTags.listTags();
- Assertions.assertArrayEquals(tags, actualTags);
+ TagList<String> tagList = supportsTags.listTagsWithVisibility();
+ Assertions.assertArrayEquals(tags, tagList.tags());
+ Assertions.assertTrue(tagList.hasInaccessibleTags());
// Return empty list
- NameListResponse resp1 = new NameListResponse(new String[0]);
+ TagNameListResponse resp1 = new TagNameListResponse(new String[0], false);
buildMockResource(Method.GET, path, null, resp1, SC_OK);
String[] actualTags1 = supportsTags.listTags();
@@ -547,16 +550,18 @@ public class TestSupportTags extends TestBase {
.withAudit(AuditDTO.builder().withCreator("test").build())
.build();
TagDTO[] tags = new TagDTO[] {tag1, tag2};
- TagListResponse resp = new TagListResponse(tags);
+ MetadataObjectTagListResponse resp = new
MetadataObjectTagListResponse(tags, true);
buildMockResource(Method.GET, path, null, resp, SC_OK);
- Tag[] actualTags = supportsTags.listTagsInfo();
+ TagList<Tag> tagList = supportsTags.listTagsInfoWithVisibility();
+ Tag[] actualTags = tagList.tags();
Assertions.assertEquals(2, actualTags.length);
Assertions.assertEquals("tag1", actualTags[0].name());
Assertions.assertEquals("tag2", actualTags[1].name());
+ Assertions.assertTrue(tagList.hasInaccessibleTags());
// Return empty list
- TagListResponse resp1 = new TagListResponse(new TagDTO[0]);
+ MetadataObjectTagListResponse resp1 = new
MetadataObjectTagListResponse(new TagDTO[0], false);
buildMockResource(Method.GET, path, null, resp1, SC_OK);
Tag[] actualTags1 = supportsTags.listTagsInfo();
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/PolicyAuthorizationIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/PolicyAuthorizationIT.java
index 5a62a433f5..309d591b60 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/PolicyAuthorizationIT.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/PolicyAuthorizationIT.java
@@ -24,7 +24,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import java.io.IOException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -40,14 +39,12 @@ import org.apache.gravitino.authorization.Privileges;
import org.apache.gravitino.authorization.SecurableObject;
import org.apache.gravitino.authorization.SecurableObjects;
import org.apache.gravitino.client.GravitinoMetalake;
-import org.apache.gravitino.client.RESTClient;
-import org.apache.gravitino.dto.responses.MetadataObjectPolicyListResponse;
-import org.apache.gravitino.dto.responses.PolicyNameListResponse;
import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.integration.test.container.ContainerSuite;
import org.apache.gravitino.integration.test.container.HiveContainer;
import org.apache.gravitino.policy.PolicyChange;
import org.apache.gravitino.policy.PolicyContents;
+import org.apache.gravitino.policy.PolicyList;
import org.apache.gravitino.policy.SupportsPolicies;
import org.apache.gravitino.rel.Column;
import org.apache.gravitino.rel.TableCatalog;
@@ -326,8 +323,13 @@ public class PolicyAuthorizationIT extends
BaseRestApiAuthorizationIT {
.listPolicies();
Assertions.assertArrayEquals(new String[] {"policy2"}, policies);
- RESTClient restClient = getNormalUserRestClient();
- assertPolicyListResponse(restClient, "table1", new String[0], true);
+ SupportsPolicies table1Policies =
+ gravitinoMetalakeLoadByNormalUser
+ .loadCatalog(CATALOG)
+ .asTableCatalog()
+ .loadTable(NameIdentifier.of(SCHEMA, "table1"))
+ .supportsPolicies();
+ assertPolicyList(table1Policies, new String[0], true);
SupportsPolicies table2Policies =
gravitinoMetalake
@@ -337,7 +339,13 @@ public class PolicyAuthorizationIT extends
BaseRestApiAuthorizationIT {
.supportsPolicies();
table2Policies.associatePolicies(new String[] {"policy1"}, new String[0]);
try {
- assertPolicyListResponse(restClient, "table2", new String[] {"policy2"},
true);
+ SupportsPolicies normalUserTable2Policies =
+ gravitinoMetalakeLoadByNormalUser
+ .loadCatalog(CATALOG)
+ .asTableCatalog()
+ .loadTable(NameIdentifier.of(SCHEMA, "table2"))
+ .supportsPolicies();
+ assertPolicyList(normalUserTable2Policies, new String[] {"policy2"},
true);
} finally {
table2Policies.associatePolicies(new String[0], new String[]
{"policy1"});
}
@@ -454,61 +462,22 @@ public class PolicyAuthorizationIT extends
BaseRestApiAuthorizationIT {
metalakeLoadByNormalUser.deletePolicy("policy2");
}
- private RESTClient getNormalUserRestClient() {
- try {
- Method restClientMethod =
-
normalUserClient.getClass().getSuperclass().getDeclaredMethod("restClient");
- restClientMethod.setAccessible(true);
- return (RESTClient) restClientMethod.invoke(normalUserClient);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("Failed to access the normal user's REST
client", e);
- }
- }
-
- private void assertPolicyListResponse(
- RESTClient restClient,
- String tableName,
+ private void assertPolicyList(
+ SupportsPolicies supportsPolicies,
String[] expectedPolicyNames,
boolean hasInaccessiblePolicies) {
- String path =
- "api/metalakes/"
- + METALAKE
- + "/objects/table/"
- + CATALOG
- + "."
- + SCHEMA
- + "."
- + tableName
- + "/policies";
- PolicyNameListResponse nameListResponse =
- restClient.get(
- path,
- PolicyNameListResponse.class,
- Map.of(),
- error -> {
- throw new AssertionError(error);
- });
- String[] actualPolicyNames = nameListResponse.getNames();
+ PolicyList<String> nameResult =
supportsPolicies.listPoliciesWithVisibility();
+ String[] actualPolicyNames = nameResult.policies();
Arrays.sort(actualPolicyNames);
Arrays.sort(expectedPolicyNames);
Assertions.assertArrayEquals(expectedPolicyNames, actualPolicyNames);
- Assertions.assertEquals(hasInaccessiblePolicies,
nameListResponse.hasInaccessiblePolicies());
+ Assertions.assertEquals(hasInaccessiblePolicies,
nameResult.hasInaccessiblePolicies());
- MetadataObjectPolicyListResponse listResponse =
- restClient.get(
- path,
- Map.of("details", "true"),
- MetadataObjectPolicyListResponse.class,
- Map.of(),
- error -> {
- throw new AssertionError(error);
- });
+ var detailsResult = supportsPolicies.listPolicyInfosWithVisibility();
String[] actualDetailNames =
- Arrays.stream(listResponse.getPolicies())
- .map(policy -> policy.name())
- .toArray(String[]::new);
+ Arrays.stream(detailsResult.policies()).map(policy ->
policy.name()).toArray(String[]::new);
Arrays.sort(actualDetailNames);
Assertions.assertArrayEquals(expectedPolicyNames, actualDetailNames);
- Assertions.assertEquals(hasInaccessiblePolicies,
listResponse.hasInaccessiblePolicies());
+ Assertions.assertEquals(hasInaccessiblePolicies,
detailsResult.hasInaccessiblePolicies());
}
}
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
index d21e327b5e..27fe580133 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/TagOperationsAuthorizationIT.java
@@ -39,10 +39,7 @@ import org.apache.gravitino.authorization.Privileges;
import org.apache.gravitino.authorization.SecurableObject;
import org.apache.gravitino.authorization.SecurableObjects;
import org.apache.gravitino.client.GravitinoMetalake;
-import org.apache.gravitino.client.RESTClient;
import org.apache.gravitino.dto.MetalakeDTO;
-import org.apache.gravitino.dto.responses.MetadataObjectTagListResponse;
-import org.apache.gravitino.dto.responses.TagNameListResponse;
import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.integration.test.container.ContainerSuite;
import org.apache.gravitino.integration.test.container.HiveContainer;
@@ -51,6 +48,7 @@ import org.apache.gravitino.rel.TableCatalog;
import org.apache.gravitino.rel.types.Types;
import org.apache.gravitino.tag.SupportsTags;
import org.apache.gravitino.tag.TagChange;
+import org.apache.gravitino.tag.TagList;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
@@ -290,31 +288,13 @@ public class TagOperationsAuthorizationIT extends
BaseRestApiAuthorizationIT {
assertEquals("tag3", tableSupportTagByNormalUser.getTag("tag3").name());
assertThrows(ForbiddenException.class, () ->
tableSupportTagByNormalUser.getTag("tag1"));
- RESTClient restClient = getNormalUserRestClient();
- String path =
- "api/metalakes/" + METALAKE + "/objects/table/" + CATALOG + "." +
SCHEMA + ".table1/tags";
- TagNameListResponse nameListResponse =
- restClient.get(
- path,
- TagNameListResponse.class,
- Map.of(),
- error -> {
- throw new AssertionError(error);
- });
- Assertions.assertEquals(2, nameListResponse.getNames().length);
- Assertions.assertTrue(nameListResponse.hasInaccessibleTags());
-
- MetadataObjectTagListResponse listResponse =
- restClient.get(
- path,
- Map.of("details", "true"),
- MetadataObjectTagListResponse.class,
- Map.of(),
- error -> {
- throw new AssertionError(error);
- });
- Assertions.assertEquals(2, listResponse.getTags().length);
- Assertions.assertTrue(listResponse.hasInaccessibleTags());
+ TagList<String> nameResult =
tableSupportTagByNormalUser.listTagsWithVisibility();
+ Assertions.assertEquals(2, nameResult.tags().length);
+ Assertions.assertTrue(nameResult.hasInaccessibleTags());
+
+ var detailsResult =
tableSupportTagByNormalUser.listTagsInfoWithVisibility();
+ Assertions.assertEquals(2, detailsResult.tags().length);
+ Assertions.assertTrue(detailsResult.hasInaccessibleTags());
String untaggedCatalog = "untagged_catalog";
Map<String, String> properties = Maps.newHashMap();
@@ -334,21 +314,18 @@ public class TagOperationsAuthorizationIT extends
BaseRestApiAuthorizationIT {
"test visibility indicators",
new HashMap<>());
try {
- String untaggedCatalogPath =
- "api/metalakes/" + METALAKE + "/objects/catalog/" + untaggedCatalog
+ "/tags";
- assertTagListResponse(untaggedCatalogPath, new String[0], false);
-
- String inheritedHiddenTagPath =
- "api/metalakes/"
- + METALAKE
- + "/objects/table/"
- + CATALOG
- + "."
- + SCHEMA
- + "."
- + tableWithOnlyInheritedHiddenTag
- + "/tags";
- assertTagListResponse(inheritedHiddenTagPath, new String[0], true);
+ SupportsTags untaggedCatalogTags =
+
normalUserClient.loadMetalake(METALAKE).loadCatalog(untaggedCatalog).supportsTags();
+ assertTagList(untaggedCatalogTags, new String[0], false);
+
+ SupportsTags inheritedHiddenTags =
+ normalUserClient
+ .loadMetalake(METALAKE)
+ .loadCatalog(CATALOG)
+ .asTableCatalog()
+ .loadTable(NameIdentifier.of(SCHEMA,
tableWithOnlyInheritedHiddenTag))
+ .supportsTags();
+ assertTagList(inheritedHiddenTags, new String[0], true);
} finally {
tableCatalog.dropTable(NameIdentifier.of(SCHEMA,
tableWithOnlyInheritedHiddenTag));
gravitinoMetalake.dropCatalog(untaggedCatalog, true);
@@ -540,47 +517,20 @@ public class TagOperationsAuthorizationIT extends
BaseRestApiAuthorizationIT {
return new Column[] {Column.of("col1", Types.StringType.get())};
}
- private RESTClient getNormalUserRestClient() {
- try {
- Method restClientMethod =
-
normalUserClient.getClass().getSuperclass().getDeclaredMethod("restClient");
- restClientMethod.setAccessible(true);
- return (RESTClient) restClientMethod.invoke(normalUserClient);
- } catch (ReflectiveOperationException e) {
- throw new RuntimeException("Failed to access the normal user's REST
client", e);
- }
- }
-
- private void assertTagListResponse(
- String path, String[] expectedTagNames, boolean hasInaccessibleTags) {
- RESTClient restClient = getNormalUserRestClient();
- TagNameListResponse nameListResponse =
- restClient.get(
- path,
- TagNameListResponse.class,
- Map.of(),
- error -> {
- throw new AssertionError(error);
- });
- String[] actualTagNames = nameListResponse.getNames();
+ private void assertTagList(
+ SupportsTags supportsTags, String[] expectedTagNames, boolean
hasInaccessibleTags) {
+ TagList<String> nameResult = supportsTags.listTagsWithVisibility();
+ String[] actualTagNames = nameResult.tags();
Arrays.sort(actualTagNames);
Arrays.sort(expectedTagNames);
Assertions.assertArrayEquals(expectedTagNames, actualTagNames);
- Assertions.assertEquals(hasInaccessibleTags,
nameListResponse.hasInaccessibleTags());
-
- MetadataObjectTagListResponse listResponse =
- restClient.get(
- path,
- Map.of("details", "true"),
- MetadataObjectTagListResponse.class,
- Map.of(),
- error -> {
- throw new AssertionError(error);
- });
+ Assertions.assertEquals(hasInaccessibleTags,
nameResult.hasInaccessibleTags());
+
+ var detailsResult = supportsTags.listTagsInfoWithVisibility();
String[] actualDetailNames =
- Arrays.stream(listResponse.getTags()).map(tag ->
tag.name()).toArray(String[]::new);
+ Arrays.stream(detailsResult.tags()).map(tag ->
tag.name()).toArray(String[]::new);
Arrays.sort(actualDetailNames);
Assertions.assertArrayEquals(expectedTagNames, actualDetailNames);
- Assertions.assertEquals(hasInaccessibleTags,
listResponse.hasInaccessibleTags());
+ Assertions.assertEquals(hasInaccessibleTags,
detailsResult.hasInaccessibleTags());
}
}
diff --git a/clients/client-python/gravitino/api/tag/__init__.py
b/clients/client-python/gravitino/api/tag/__init__.py
index 28b1469c2f..f464455d71 100644
--- a/clients/client-python/gravitino/api/tag/__init__.py
+++ b/clients/client-python/gravitino/api/tag/__init__.py
@@ -19,8 +19,10 @@ from __future__ import annotations
from gravitino.api.tag.tag import Tag
from gravitino.api.tag.tag_change import TagChange
+from gravitino.api.tag.tag_list import TagList
__all__ = [
"Tag",
"TagChange",
+ "TagList",
]
diff --git a/clients/client-python/gravitino/api/tag/supports_tags.py
b/clients/client-python/gravitino/api/tag/supports_tags.py
index f1e9a20510..dba4f487c6 100644
--- a/clients/client-python/gravitino/api/tag/supports_tags.py
+++ b/clients/client-python/gravitino/api/tag/supports_tags.py
@@ -20,6 +20,7 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
class SupportsTags(ABC):
@@ -37,6 +38,14 @@ class SupportsTags(ABC):
"""
pass
+ def list_tags_with_visibility(self) -> TagList[str]:
+ """List visible tag names and report whether inaccessible tags were
omitted.
+
+ The compatibility default cannot detect omitted tags. Implementations
backed by
+ authorization-aware APIs should override this method.
+ """
+ return TagList(self.list_tags(), False)
+
@abstractmethod
def list_tags_info(self) -> list[Tag]:
"""List all the tags with details for the specific object.
@@ -46,6 +55,14 @@ class SupportsTags(ABC):
"""
pass
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ """List visible tag details and report whether inaccessible tags were
omitted.
+
+ The compatibility default cannot detect omitted tags. Implementations
backed by
+ authorization-aware APIs should override this method.
+ """
+ return TagList(self.list_tags_info(), False)
+
@abstractmethod
def get_tag(self, name: str) -> Tag:
"""Get a tag by its name for the specific object.
diff --git a/clients/client-python/gravitino/api/tag/__init__.py
b/clients/client-python/gravitino/api/tag/tag_list.py
similarity index 56%
copy from clients/client-python/gravitino/api/tag/__init__.py
copy to clients/client-python/gravitino/api/tag/tag_list.py
index 28b1469c2f..e7ed58ac17 100644
--- a/clients/client-python/gravitino/api/tag/__init__.py
+++ b/clients/client-python/gravitino/api/tag/tag_list.py
@@ -17,10 +17,22 @@
from __future__ import annotations
-from gravitino.api.tag.tag import Tag
-from gravitino.api.tag.tag_change import TagChange
+from typing import Generic, TypeVar
-__all__ = [
- "Tag",
- "TagChange",
-]
+T = TypeVar("T")
+
+
+class TagList(Generic[T]):
+ """A list of visible tags and whether authorization filtering omitted
others."""
+
+ def __init__(self, tags: list[T], has_inaccessible_tags: bool):
+ self._tags = list(tags)
+ self._has_inaccessible_tags = has_inaccessible_tags
+
+ def tags(self) -> list[T]:
+ """Return a copy of the visible tag names or tag objects."""
+ return list(self._tags)
+
+ def has_inaccessible_tags(self) -> bool:
+ """Return whether one or more inaccessible tags were omitted."""
+ return self._has_inaccessible_tags
diff --git a/clients/client-python/gravitino/client/base_schema_catalog.py
b/clients/client-python/gravitino/client/base_schema_catalog.py
index a2cbae094b..64f9c482d0 100644
--- a/clients/client-python/gravitino/client/base_schema_catalog.py
+++ b/clients/client-python/gravitino/client/base_schema_catalog.py
@@ -31,6 +31,7 @@ from gravitino.api.schema_change import SchemaChange
from gravitino.api.supports_schemas import SupportsSchemas
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.function_catalog_operations import
FunctionCatalogOperations
from gravitino.client.generic_schema import GenericSchema
from gravitino.client.metadata_object_credential_operations import (
@@ -357,9 +358,15 @@ class BaseSchemaCatalog(
def list_tags(self) -> List[str]:
return self._object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> List[Tag]:
return self._object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return self._object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._object_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/client/generic_column.py
b/clients/client-python/gravitino/client/generic_column.py
index dbb473aaa6..40bc96c59f 100644
--- a/clients/client-python/gravitino/client/generic_column.py
+++ b/clients/client-python/gravitino/client/generic_column.py
@@ -24,6 +24,7 @@ from gravitino.api.rel.expressions.expression import
Expression
from gravitino.api.rel.types.type import Type
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.utils import HTTPClient
@@ -84,9 +85,15 @@ class GenericColumn(Column, SupportsTags):
def list_tags(self) -> list[str]:
return self._object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> list[Tag]:
return self._object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return self._object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._object_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/client/generic_fileset.py
b/clients/client-python/gravitino/client/generic_fileset.py
index 1aa0be9685..b2d6a42023 100644
--- a/clients/client-python/gravitino/client/generic_fileset.py
+++ b/clients/client-python/gravitino/client/generic_fileset.py
@@ -23,6 +23,7 @@ from gravitino.api.metadata_object import MetadataObject
from gravitino.api.metadata_objects import MetadataObjects
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.metadata_object_credential_operations import (
MetadataObjectCredentialOperations,
)
@@ -89,9 +90,15 @@ class GenericFileset(
def list_tags(self) -> List[str]:
return self._object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> List[Tag]:
return self._object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return self._object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._object_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/client/generic_function.py
b/clients/client-python/gravitino/client/generic_function.py
index 2ef6bdfd72..785d0396c7 100644
--- a/clients/client-python/gravitino/client/generic_function.py
+++ b/clients/client-python/gravitino/client/generic_function.py
@@ -24,6 +24,7 @@ from gravitino.api.metadata_object import MetadataObject
from gravitino.api.metadata_objects import MetadataObjects
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.dto.audit_dto import AuditDTO
from gravitino.dto.function.function_dto import FunctionDTO
@@ -90,10 +91,18 @@ class GenericFunction(Function, SupportsTags):
"""List the tag names associated with the function."""
return self._object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ """List visible tag names and authorization-filtering status."""
+ return self._object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> list[Tag]:
"""List the tags associated with the function."""
return self._object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ """List visible tag details and authorization-filtering status."""
+ return self._object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
"""Get an associated tag by name."""
return self._object_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/client/generic_model.py
b/clients/client-python/gravitino/client/generic_model.py
index 2cfbc87369..96f10b56fc 100644
--- a/clients/client-python/gravitino/client/generic_model.py
+++ b/clients/client-python/gravitino/client/generic_model.py
@@ -22,7 +22,7 @@ from typing import Optional
from gravitino.api.metadata_object import MetadataObject
from gravitino.api.metadata_objects import MetadataObjects
from gravitino.api.model.model import Model
-from gravitino.api.tag import Tag
+from gravitino.api.tag import Tag, TagList
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.dto.audit_dto import AuditDTO
@@ -74,9 +74,15 @@ class GenericModel(Model, SupportsTags):
def list_tags(self) -> list[str]:
return self._model_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._model_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> list[Tag]:
return self._model_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return self._model_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._model_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/client/generic_schema.py
b/clients/client-python/gravitino/client/generic_schema.py
index 8301e6ab87..e55225cde2 100644
--- a/clients/client-python/gravitino/client/generic_schema.py
+++ b/clients/client-python/gravitino/client/generic_schema.py
@@ -25,6 +25,7 @@ from gravitino.api.metadata_objects import MetadataObjects
from gravitino.api.schema import Schema
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.dto.schema_dto import SchemaDTO
from gravitino.utils.http_client import HTTPClient
@@ -79,9 +80,15 @@ class GenericSchema(
def list_tags(self) -> list[str]:
return self._metadata_object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._metadata_object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> list[Tag]:
return self._metadata_object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return
self._metadata_object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._metadata_object_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/client/generic_view.py
b/clients/client-python/gravitino/client/generic_view.py
index 991a2e4ce0..de688e0087 100644
--- a/clients/client-python/gravitino/client/generic_view.py
+++ b/clients/client-python/gravitino/client/generic_view.py
@@ -25,6 +25,7 @@ from gravitino.api.rel.representation import Representation
from gravitino.api.rel.view import View
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.metadata_object_tag_operations import
MetadataObjectTagOperations
from gravitino.dto.rel.view_dto import ViewDTO
from gravitino.namespace import Namespace
@@ -89,9 +90,15 @@ class GenericView(View, SupportsTags):
def list_tags(self) -> list[str]:
return self._object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> list[Tag]:
return self._object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return self._object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._object_tag_operations.get_tag(name)
diff --git
a/clients/client-python/gravitino/client/metadata_object_tag_operations.py
b/clients/client-python/gravitino/client/metadata_object_tag_operations.py
index 8f46ed0c6d..ea9e6816f6 100644
--- a/clients/client-python/gravitino/client/metadata_object_tag_operations.py
+++ b/clients/client-python/gravitino/client/metadata_object_tag_operations.py
@@ -20,6 +20,7 @@ from __future__ import annotations
from gravitino.api.metadata_object import MetadataObject
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.generic_tag import GenericTag
from gravitino.dto.requests.tag_associate_request import TagsAssociateRequest
from gravitino.dto.responses.tag_response import (
@@ -59,6 +60,9 @@ class MetadataObjectTagOperations(SupportsTags):
)
def list_tags(self) -> list[str]:
+ return self.list_tags_with_visibility().tags()
+
+ def list_tags_with_visibility(self) -> TagList[str]:
response = self.rest_client.get(
self.tag_request_path,
params={},
@@ -69,9 +73,14 @@ class MetadataObjectTagOperations(SupportsTags):
)
list_tags_resp.validate()
- return list_tags_resp.tag_names()
+ return TagList(
+ list_tags_resp.tag_names(), list_tags_resp.has_inaccessible_tags()
+ )
def list_tags_info(self) -> list[Tag]:
+ return self.list_tags_info_with_visibility().tags()
+
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
response = self.rest_client.get(
self.tag_request_path,
params={"details": "true"},
@@ -81,14 +90,17 @@ class MetadataObjectTagOperations(SupportsTags):
list_info_resp = TagListResponse.from_json(response.body,
infer_missing=True)
list_info_resp.validate()
- return [
- GenericTag(
- self.metalake_name,
- tag_dto,
- self.rest_client,
- )
- for tag_dto in list_info_resp.tags()
- ]
+ return TagList(
+ [
+ GenericTag(
+ self.metalake_name,
+ tag_dto,
+ self.rest_client,
+ )
+ for tag_dto in list_info_resp.tags()
+ ],
+ list_info_resp.has_inaccessible_tags(),
+ )
def get_tag(self, name: str) -> Tag:
Precondition.check_argument(
diff --git a/clients/client-python/gravitino/client/relational_table.py
b/clients/client-python/gravitino/client/relational_table.py
index b35e868969..b106c27aa0 100644
--- a/clients/client-python/gravitino/client/relational_table.py
+++ b/clients/client-python/gravitino/client/relational_table.py
@@ -32,6 +32,7 @@ from gravitino.api.stats.statistic_value import StatisticValue
from gravitino.api.stats.supports_statistics import SupportsStatistics
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag import Tag
+from gravitino.api.tag.tag_list import TagList
from gravitino.client.generic_column import GenericColumn
from gravitino.client.metadata_object_statistics_operations import (
MetadataObjectStatisticsOperations,
@@ -246,9 +247,15 @@ class RelationalTable(
def list_tags(self) -> list[str]:
return self._object_tag_operations.list_tags()
+ def list_tags_with_visibility(self) -> TagList[str]:
+ return self._object_tag_operations.list_tags_with_visibility()
+
def list_tags_info(self) -> list[Tag]:
return self._object_tag_operations.list_tags_info()
+ def list_tags_info_with_visibility(self) -> TagList[Tag]:
+ return self._object_tag_operations.list_tags_info_with_visibility()
+
def get_tag(self, name: str) -> Tag:
return self._object_tag_operations.get_tag(name)
diff --git a/clients/client-python/gravitino/dto/responses/tag_response.py
b/clients/client-python/gravitino/dto/responses/tag_response.py
index e0dea09e71..d72e9c27e2 100644
--- a/clients/client-python/gravitino/dto/responses/tag_response.py
+++ b/clients/client-python/gravitino/dto/responses/tag_response.py
@@ -20,7 +20,6 @@ from __future__ import annotations
from dataclasses import dataclass, field
from dataclasses_json import config, dataclass_json
-
from gravitino.dto.responses.base_response import BaseResponse
from gravitino.dto.tag_dto import TagDTO
from gravitino.utils.precondition import Precondition
@@ -32,10 +31,16 @@ class TagNamesListResponse(BaseResponse):
"""Represents a response for a Tag Names List request."""
_tags: list[str] = field(default_factory=list,
metadata=config(field_name="names"))
+ _has_inaccessible_tags: bool = field(
+ default=False, metadata=config(field_name="hasInaccessibleTags")
+ )
def tag_names(self) -> list[str]:
return self._tags
+ def has_inaccessible_tags(self) -> bool:
+ return self._has_inaccessible_tags
+
def validate(self) -> None:
Precondition.check_argument(
self._tags is not None, "Tag Names List response should have tags"
@@ -55,10 +60,16 @@ class TagListResponse(BaseResponse):
_tags: list[TagDTO] = field(
default_factory=list, metadata=config(field_name="tags")
)
+ _has_inaccessible_tags: bool = field(
+ default=False, metadata=config(field_name="hasInaccessibleTags")
+ )
def tags(self) -> list[TagDTO]:
return self._tags
+ def has_inaccessible_tags(self) -> bool:
+ return self._has_inaccessible_tags
+
@dataclass_json
@dataclass
diff --git a/clients/client-python/tests/unittests/mock_base.py
b/clients/client-python/tests/unittests/mock_base.py
index 3f1cd15494..d0ec2a7129 100644
--- a/clients/client-python/tests/unittests/mock_base.py
+++ b/clients/client-python/tests/unittests/mock_base.py
@@ -209,7 +209,9 @@ def mock_load_fileset(name: str, location: str):
_audit=audit_dto,
)
return GenericFileset(
- fileset, None, Namespace.of("metalake_demo", "fileset_catalog", "tmp")
+ fileset,
+ HTTPClient("http://localhost:9090", is_debug=True),
+ Namespace.of("metalake_demo", "fileset_catalog", "tmp"),
)
diff --git a/clients/client-python/tests/unittests/test_tag_api.py
b/clients/client-python/tests/unittests/test_tag_api.py
index 0163ba7c3a..5019d03caf 100644
--- a/clients/client-python/tests/unittests/test_tag_api.py
+++ b/clients/client-python/tests/unittests/test_tag_api.py
@@ -25,14 +25,14 @@ from gravitino.api.tag import Tag
from gravitino.api.tag.supports_tags import SupportsTags
from gravitino.api.tag.tag_change import TagChange
from gravitino.client.generic_tag import GenericTag
-from gravitino.exceptions.base import IllegalArgumentException
from gravitino.dto.responses.drop_response import DropResponse
-from gravitino.name_identifier import NameIdentifier
from gravitino.dto.responses.tag_response import (
TagListResponse,
TagNamesListResponse,
TagResponse,
)
+from gravitino.exceptions.base import IllegalArgumentException
+from gravitino.name_identifier import NameIdentifier
from tests.unittests import mock_base
@@ -40,6 +40,48 @@ from tests.unittests import mock_base
class TestTagAPI(unittest.TestCase):
_metalake_name: str = "metalake_demo"
+ def test_new_tag_api_compatibility_defaults(self, *mock_method) -> None:
+ class LegacyTag(Tag):
+ def name(self) -> str:
+ return "tagA"
+
+ def comment(self) -> str:
+ return "comment"
+
+ def properties(self) -> dict[str, str]:
+ return {}
+
+ def audit_info(self):
+ return None
+
+ def inherited(self):
+ return None
+
+ class LegacySupportsTags(SupportsTags):
+ def list_tags(self) -> list[str]:
+ return []
+
+ def list_tags_info(self) -> list[Tag]:
+ return []
+
+ def get_tag(self, name: str) -> Tag:
+ return LegacyTag()
+
+ def associate_tags(
+ self, tags_to_add: list[str], tags_to_remove: list[str]
+ ) -> list[str]:
+ return []
+
+ supports_tags = LegacySupportsTags()
+ self.assertEqual([], supports_tags.list_tags_with_visibility().tags())
+ self.assertFalse(
+ supports_tags.list_tags_with_visibility().has_inaccessible_tags()
+ )
+ self.assertEqual([],
supports_tags.list_tags_info_with_visibility().tags())
+ self.assertFalse(
+
supports_tags.list_tags_info_with_visibility().has_inaccessible_tags()
+ )
+
def test_client_get_tag(self, *mock_method) -> None:
with mock_base.mock_tag_methods():
client = GravitinoClient(
@@ -528,6 +570,24 @@ class TestObjectTagAPI(unittest.TestCase):
self.assertIn("tagA", tags)
self.assertIn("tagB", tags)
+ def test_fileset_list_tags_with_visibility(self, *mock_method) -> None:
+ """Test listing visible tag names and authorization-filtering
status."""
+ client = GravitinoClient(
+ uri="http://localhost:8090",
+ metalake_name=self._metalake_name,
+ check_version=False,
+ )
+ fileset = self._load_fileset(client)
+ response = TagNamesListResponse(0, ["tagA"], True)
+
+ with patch(
+ "gravitino.utils.http_client.HTTPClient.get",
+ return_value=mock_base.mock_http_response(response.to_json()),
+ ):
+ tag_list = fileset.list_tags_with_visibility()
+ self.assertEqual(["tagA"], tag_list.tags())
+ self.assertTrue(tag_list.has_inaccessible_tags())
+
def test_fileset_list_tags_info(self, *mock_method) -> None:
"""Test listing tags with details on a fileset via REST API."""
tag_dto = mock_base.build_tag_dto("tagA", "comment A")
@@ -550,6 +610,24 @@ class TestObjectTagAPI(unittest.TestCase):
self.assertEqual("tagA", tags[0].name())
self.assertEqual("comment A", tags[0].comment())
+ def test_fileset_list_tags_info_with_visibility(self, *mock_method) ->
None:
+ """Test listing visible tag details and authorization-filtering
status."""
+ client = GravitinoClient(
+ uri="http://localhost:8090",
+ metalake_name=self._metalake_name,
+ check_version=False,
+ )
+ fileset = self._load_fileset(client)
+ response = TagListResponse(0, [mock_base.build_tag_dto("tagA")], True)
+
+ with patch(
+ "gravitino.utils.http_client.HTTPClient.get",
+ return_value=mock_base.mock_http_response(response.to_json()),
+ ):
+ tag_list = fileset.list_tags_info_with_visibility()
+ self.assertEqual(["tagA"], [tag.name() for tag in tag_list.tags()])
+ self.assertTrue(tag_list.has_inaccessible_tags())
+
def test_fileset_get_tag(self, *mock_method) -> None:
"""Test getting a specific tag on a fileset via REST API."""
tag_dto = mock_base.build_tag_dto("tagA", "comment A")
diff --git a/docs/manage-policies-in-gravitino.md
b/docs/manage-policies-in-gravitino.md
index a8face785f..f1edbba297 100644
--- a/docs/manage-policies-in-gravitino.md
+++ b/docs/manage-policies-in-gravitino.md
@@ -252,7 +252,11 @@ schema.supportsPolicies().associatePolicies(new String[]
{"retention_30d"}, null
### List Policies on an Object
The response includes policies inherited from ancestors. With `details=true`
each policy carries an
-`inherited` field, which a plain name listing does not.
+`inherited` field, which a plain name listing does not. Both response modes
include
+`hasInaccessiblePolicies`. Gravitino resolves direct and inherited policy
associations before
+authorization filtering, so the flag also covers inaccessible inherited
policies. The flag reveals
+only that at least one policy was omitted; it does not reveal the policy's
name, count, details, or
+inheritance source.
<Tabs groupId='language' queryString>
<TabItem value="shell" label="REST">
@@ -262,18 +266,45 @@ curl -X GET -H "Accept:
application/vnd.gravitino.v1+json" \
"http://localhost:8090/api/metalakes/test/objects/catalog/catalog1/policies?details=true"
```
+If every effective policy is inaccessible, the response contains an empty list
and sets the flag:
+
+```json
+{
+ "code": 0,
+ "policies": [],
+ "hasInaccessiblePolicies": true
+}
+```
+
</TabItem>
<TabItem value="java" label="Java">
```java
Catalog catalog = client.loadCatalog("catalog1");
-String[] policyNames = catalog.supportsPolicies().listPolicies();
-Policy[] policies = catalog.supportsPolicies().listPolicyInfos();
+PolicyList<String> nameList =
catalog.supportsPolicies().listPoliciesWithVisibility();
+String[] policyNames = nameList.policies();
+boolean omittedPolicies = nameList.hasInaccessiblePolicies();
+
+PolicyList<Policy> detailList =
catalog.supportsPolicies().listPolicyInfosWithVisibility();
+Policy[] policies = detailList.policies();
+boolean omittedPolicyDetails = detailList.hasInaccessiblePolicies();
```
+The existing `listPolicies()` and `listPolicyInfos()` methods continue to
return only the visible
+arrays.
+
</TabItem>
</Tabs>
+Interpret the visible list and `hasInaccessiblePolicies` together:
+
+| Visible list | `hasInaccessiblePolicies` | Meaning |
+|---|---:|---|
+| Empty | `false` | The object has no effective policy associations. |
+| Empty | `true` | All effective policy associations are inaccessible. |
+| Non-empty | `true` | The response includes visible policies and omits at
least one inaccessible policy. |
+| Non-empty | `false` | All effective policy associations are visible. |
+
### Get One Policy on an Object
<Tabs groupId='language' queryString>
diff --git a/docs/manage-tags-in-gravitino.md b/docs/manage-tags-in-gravitino.md
index 016709b7ac..3d81615d1a 100644
--- a/docs/manage-tags-in-gravitino.md
+++ b/docs/manage-tags-in-gravitino.md
@@ -246,7 +246,11 @@ raw_events.supports_tags().associate_tags(["pii"], None)
### List Tags on an Object
The response includes tags inherited from ancestors. With `details=true` each
tag carries an
-`inherited` field, which a plain name listing does not.
+`inherited` field, which a plain name listing does not. Both response modes
include
+`hasInaccessibleTags`. Gravitino resolves direct and inherited tag
associations before
+authorization filtering, so the flag also covers inaccessible inherited tags.
The flag reveals
+only that at least one tag was omitted; it does not reveal the tag's name,
count, details, or
+inheritance source.
<Tabs groupId='language' queryString>
<TabItem value="shell" label="REST">
@@ -256,27 +260,60 @@ curl -X GET -H "Accept:
application/vnd.gravitino.v1+json" \
"http://localhost:8090/api/metalakes/test/objects/table/catalog1.schema1.customers/tags?details=true"
```
+If every effective tag is inaccessible, the response contains an empty list
and sets the flag:
+
+```json
+{
+ "code": 0,
+ "tags": [],
+ "hasInaccessibleTags": true
+}
+```
+
</TabItem>
<TabItem value="java" label="Java">
```java
Table customers = ...
-String[] tagNames = customers.supportsTags().listTags();
-Tag[] tags = customers.supportsTags().listTagsInfo();
+TagList<String> nameList = customers.supportsTags().listTagsWithVisibility();
+String[] tagNames = nameList.tags();
+boolean omittedTags = nameList.hasInaccessibleTags();
+
+TagList<Tag> detailList =
customers.supportsTags().listTagsInfoWithVisibility();
+Tag[] tags = detailList.tags();
+boolean omittedTagDetails = detailList.hasInaccessibleTags();
```
+The existing `listTags()` and `listTagsInfo()` methods continue to return only
the visible arrays.
+
</TabItem>
<TabItem value="python" label="Python">
```python
customers = ...
-tag_names = customers.supports_tags().list_tags()
-tags = customers.supports_tags().list_tags_info()
+name_list = customers.supports_tags().list_tags_with_visibility()
+tag_names = name_list.tags()
+omitted_tags = name_list.has_inaccessible_tags()
+
+detail_list = customers.supports_tags().list_tags_info_with_visibility()
+tags = detail_list.tags()
+omitted_tag_details = detail_list.has_inaccessible_tags()
```
+The existing `list_tags()` and `list_tags_info()` methods continue to return
only the visible lists.
+
</TabItem>
</Tabs>
+Interpret the visible list and `hasInaccessibleTags` together:
+
+| Visible list | `hasInaccessibleTags` | Meaning |
+|---|---:|---|
+| Empty | `false` | The object has no effective tag associations. |
+| Empty | `true` | All effective tag associations are inaccessible. |
+| Non-empty | `true` | The response includes visible tags and omits at least
one inaccessible tag. |
+| Non-empty | `false` | All effective tag associations are visible. |
+
### Get One Tag on an Object
<Tabs groupId='language' queryString>