Skip to content

Commit 364b53c

Browse files
Fix Namespace resolution on grant/revoke privilege operations (#2170)
* Fix Namespace resolution on grant/revoke privilege operations * Move isFullyResolvedNamespace to PolarisResolvedPathWrapper
1 parent 756e535 commit 364b53c

File tree

4 files changed

+476
-2
lines changed

4 files changed

+476
-2
lines changed

polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisResolvedPathWrapper.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
*/
1919
package org.apache.polaris.core.persistence;
2020

21+
import jakarta.annotation.Nonnull;
22+
import java.util.Iterator;
2123
import java.util.List;
2224
import java.util.stream.Collectors;
25+
import org.apache.iceberg.catalog.Namespace;
2326
import org.apache.polaris.core.entity.PolarisEntity;
27+
import org.apache.polaris.core.entity.PolarisEntityType;
2428

2529
/**
2630
* Holds fully-resolved path of PolarisEntities representing the targetEntity with all its grants
@@ -77,6 +81,43 @@ public List<PolarisEntity> getRawParentPath() {
7781
.collect(Collectors.toList());
7882
}
7983

84+
/**
85+
* Checks if a namespace is fully resolved.
86+
*
87+
* @param catalogName the name of the catalog
88+
* @param namespace the namespace we're trying to resolve
89+
* @return true if the namespace is considered fully resolved for the given catalog type
90+
*/
91+
public boolean isFullyResolvedNamespace(
92+
@Nonnull String catalogName, @Nonnull Namespace namespace) {
93+
if (resolvedPath == null) {
94+
return false;
95+
}
96+
97+
List<PolarisEntity> fullPath = getRawFullPath();
98+
int expectedPathLength = 1 + namespace.levels().length;
99+
if (fullPath.size() != expectedPathLength) {
100+
return false;
101+
}
102+
103+
if (!fullPath.get(0).getName().equals(catalogName)) {
104+
return false;
105+
}
106+
107+
String[] namespaceLevels = namespace.levels();
108+
int levelsLength = namespaceLevels.length;
109+
Iterator<PolarisEntity> fullPathIterator = fullPath.iterator();
110+
fullPathIterator.next();
111+
for (int i = 0; i < levelsLength; i++) {
112+
PolarisEntity entity = fullPathIterator.next();
113+
if (!entity.getName().equals(namespaceLevels[i])
114+
|| entity.getType() != PolarisEntityType.NAMESPACE) {
115+
return false;
116+
}
117+
}
118+
return true;
119+
}
120+
80121
@Override
81122
public String toString() {
82123
return "resolvedPath:" + resolvedPath;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.core.persistence;
20+
21+
import static org.junit.jupiter.api.Assertions.assertFalse;
22+
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
24+
import java.util.List;
25+
import org.apache.iceberg.catalog.Namespace;
26+
import org.apache.polaris.core.entity.PolarisEntity;
27+
import org.apache.polaris.core.entity.PolarisEntityType;
28+
import org.junit.jupiter.api.Test;
29+
30+
public class PolarisResolvedPathWrapperTest {
31+
32+
@Test
33+
void testIsFullyResolvedNamespace_NullResolvedPath() {
34+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(null);
35+
36+
assertFalse(wrapper.isFullyResolvedNamespace("test-catalog", Namespace.of("ns1")));
37+
}
38+
39+
@Test
40+
void testIsFullyResolvedNamespace_InsufficientPathLength() {
41+
String catalogName = "test-catalog";
42+
Namespace namespace = Namespace.of("ns1", "ns2");
43+
44+
PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
45+
List<ResolvedPolarisEntity> shortPath = List.of(createResolvedEntity(catalogEntity));
46+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(shortPath);
47+
48+
assertFalse(wrapper.isFullyResolvedNamespace(catalogName, namespace));
49+
}
50+
51+
@Test
52+
void testIsFullyResolvedNamespace_WrongCatalogName() {
53+
String catalogName = "test-catalog";
54+
String wrongCatalogName = "wrong-catalog";
55+
Namespace namespace = Namespace.of("ns1");
56+
57+
PolarisEntity wrongCatalogEntity = createEntity(wrongCatalogName, PolarisEntityType.CATALOG);
58+
PolarisEntity namespaceEntity = createEntity("ns1", PolarisEntityType.NAMESPACE);
59+
List<ResolvedPolarisEntity> path =
60+
List.of(createResolvedEntity(wrongCatalogEntity), createResolvedEntity(namespaceEntity));
61+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(path);
62+
63+
assertFalse(wrapper.isFullyResolvedNamespace(catalogName, namespace));
64+
}
65+
66+
@Test
67+
void testIsFullyResolvedNamespace_WrongNamespaceNames() {
68+
String catalogName = "test-catalog";
69+
Namespace namespace = Namespace.of("ns1", "ns2");
70+
71+
PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
72+
PolarisEntity namespace1Entity = createEntity("ns1", PolarisEntityType.NAMESPACE);
73+
PolarisEntity namespace2WrongEntity = createEntity("wrong-ns2", PolarisEntityType.NAMESPACE);
74+
List<ResolvedPolarisEntity> path =
75+
List.of(
76+
createResolvedEntity(catalogEntity),
77+
createResolvedEntity(namespace1Entity),
78+
createResolvedEntity(namespace2WrongEntity));
79+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(path);
80+
81+
assertFalse(wrapper.isFullyResolvedNamespace(catalogName, namespace));
82+
}
83+
84+
@Test
85+
void testIsFullyResolvedNamespace_CorrectPath() {
86+
String catalogName = "test-catalog";
87+
Namespace namespace = Namespace.of("ns1", "ns2");
88+
89+
PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
90+
PolarisEntity namespace1Entity = createEntity("ns1", PolarisEntityType.NAMESPACE);
91+
PolarisEntity namespace2Entity = createEntity("ns2", PolarisEntityType.NAMESPACE);
92+
List<ResolvedPolarisEntity> path =
93+
List.of(
94+
createResolvedEntity(catalogEntity),
95+
createResolvedEntity(namespace1Entity),
96+
createResolvedEntity(namespace2Entity));
97+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(path);
98+
99+
assertTrue(wrapper.isFullyResolvedNamespace(catalogName, namespace));
100+
}
101+
102+
@Test
103+
void testIsFullyResolvedNamespace_WrongEntityType() {
104+
String catalogName = "test-catalog";
105+
Namespace namespace = Namespace.of("ns1", "ns2");
106+
107+
PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
108+
PolarisEntity correctEntityType = createEntity("ns1", PolarisEntityType.NAMESPACE);
109+
PolarisEntity wrongEntityType = createEntity("ns2", PolarisEntityType.TABLE_LIKE);
110+
List<ResolvedPolarisEntity> path =
111+
List.of(
112+
createResolvedEntity(catalogEntity),
113+
createResolvedEntity(correctEntityType),
114+
createResolvedEntity(wrongEntityType));
115+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(path);
116+
117+
assertFalse(wrapper.isFullyResolvedNamespace(catalogName, namespace));
118+
}
119+
120+
@Test
121+
void testIsFullyResolvedNamespace_EmptyNamespace() {
122+
String catalogName = "test-catalog";
123+
Namespace namespace = Namespace.empty();
124+
125+
PolarisEntity catalogEntity = createEntity(catalogName, PolarisEntityType.CATALOG);
126+
List<ResolvedPolarisEntity> path = List.of(createResolvedEntity(catalogEntity));
127+
PolarisResolvedPathWrapper wrapper = new PolarisResolvedPathWrapper(path);
128+
129+
assertTrue(wrapper.isFullyResolvedNamespace(catalogName, namespace));
130+
}
131+
132+
private PolarisEntity createEntity(String name, PolarisEntityType type) {
133+
return new PolarisEntity.Builder()
134+
.setName(name)
135+
.setType(type)
136+
.setId(1L)
137+
.setCatalogId(1L)
138+
.setParentId(1L)
139+
.setCreateTimestamp(System.currentTimeMillis())
140+
.build();
141+
}
142+
143+
private ResolvedPolarisEntity createResolvedEntity(PolarisEntity entity) {
144+
return new ResolvedPolarisEntity(entity, null, null);
145+
}
146+
}

service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,7 +1684,8 @@ public boolean grantPrivilegeOnNamespaceToRole(
16841684
.orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName));
16851685

16861686
PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(namespace);
1687-
if (resolvedPathWrapper == null) {
1687+
if (resolvedPathWrapper == null
1688+
|| !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
16881689
throw new NotFoundException("Namespace %s not found", namespace);
16891690
}
16901691
List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
@@ -1712,7 +1713,8 @@ public boolean revokePrivilegeOnNamespaceFromRole(
17121713
.orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName));
17131714

17141715
PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(namespace);
1715-
if (resolvedPathWrapper == null) {
1716+
if (resolvedPathWrapper == null
1717+
|| !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
17161718
throw new NotFoundException("Namespace %s not found", namespace);
17171719
}
17181720
List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();

0 commit comments

Comments
 (0)