Skip to content

Commit a5cbc49

Browse files
Support Namespace/Table level RBAC for external passthrough catalogs
1 parent 364b53c commit a5cbc49

File tree

2 files changed

+481
-29
lines changed

2 files changed

+481
-29
lines changed

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

Lines changed: 176 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,8 @@ private void authorizeGrantOnTableLikeOperationOrThrow(
520520
PolarisResolvedPathWrapper tableLikeWrapper =
521521
resolutionManifest.getResolvedPath(
522522
identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE, true);
523-
if (!subTypes.contains(tableLikeWrapper.getRawLeafEntity().getSubType())) {
523+
if (!resolutionManifest.getIsPassthroughFacade()
524+
&& !subTypes.contains(tableLikeWrapper.getRawLeafEntity().getSubType())) {
524525
CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier, subTypes);
525526
}
526527

@@ -1679,14 +1680,29 @@ public boolean grantPrivilegeOnNamespaceToRole(
16791680
PolarisAuthorizableOperation.ADD_NAMESPACE_GRANT_TO_CATALOG_ROLE;
16801681
authorizeGrantOnNamespaceOperationOrThrow(op, catalogName, namespace, catalogRoleName);
16811682

1683+
CatalogEntity catalogEntity =
1684+
findCatalogByName(catalogName)
1685+
.orElseThrow(() -> new NotFoundException("Parent catalog %s not found", catalogName));
16821686
PolarisEntity catalogRoleEntity =
16831687
findCatalogRoleByName(catalogName, catalogRoleName)
16841688
.orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName));
16851689

16861690
PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(namespace);
16871691
if (resolvedPathWrapper == null
16881692
|| !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
1689-
throw new NotFoundException("Namespace %s not found", namespace);
1693+
if (resolutionManifest.getIsPassthroughFacade()) {
1694+
resolvedPathWrapper =
1695+
createSyntheticNamespaceEntities(catalogEntity, namespace, resolvedPathWrapper);
1696+
if (resolvedPathWrapper == null
1697+
|| !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, namespace)) {
1698+
throw new RuntimeException(
1699+
String.format(
1700+
"Failed to create synthetic namespace entities for namespace %s in catalog %s",
1701+
namespace.toString(), catalogName));
1702+
}
1703+
} else {
1704+
throw new NotFoundException("Namespace %s not found", namespace);
1705+
}
16901706
}
16911707
List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
16921708
PolarisEntity namespaceEntity = resolvedPathWrapper.getRawLeafEntity();
@@ -1730,6 +1746,79 @@ public boolean revokePrivilegeOnNamespaceFromRole(
17301746
.isSuccess();
17311747
}
17321748

1749+
/**
1750+
* Creates and persists the missing synthetic namespace entities for external catalogs.
1751+
*
1752+
* @param catalogEntity the external passthrough facade catalog entity.
1753+
* @param namespace the expected fully resolved namespace to be created.
1754+
* @param existingPath the partially resolved path currently stored in the metastore.
1755+
* @return the fully resolved path wrapper.
1756+
*/
1757+
private PolarisResolvedPathWrapper createSyntheticNamespaceEntities(
1758+
CatalogEntity catalogEntity, Namespace namespace, PolarisResolvedPathWrapper existingPath) {
1759+
1760+
if (existingPath == null) {
1761+
throw new IllegalStateException(
1762+
String.format("Catalog entity %s does not exist.", catalogEntity.getName()));
1763+
}
1764+
1765+
List<PolarisEntity> completePath = new ArrayList<>(existingPath.getRawFullPath());
1766+
PolarisEntity currentParent = existingPath.getRawLeafEntity();
1767+
1768+
String[] allNamespaceLevels = namespace.levels();
1769+
int matchingLevel = -1;
1770+
for (PolarisEntity entity : completePath.subList(1, completePath.size())) {
1771+
if (entity.getName().equals(allNamespaceLevels[matchingLevel + 1])) {
1772+
matchingLevel++;
1773+
} else {
1774+
break;
1775+
}
1776+
}
1777+
1778+
for (int i = matchingLevel + 1; i < allNamespaceLevels.length; i++) {
1779+
String namespacePart = allNamespaceLevels[i];
1780+
1781+
// TODO: Instead of creating synthetic entitties, rely on external catalog mediated backfill.
1782+
PolarisEntity syntheticNamespace =
1783+
new PolarisEntity.Builder()
1784+
.setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
1785+
.setCatalogId(catalogEntity.getId())
1786+
.setParentId(currentParent.getId())
1787+
.setType(PolarisEntityType.NAMESPACE)
1788+
.setName(namespacePart)
1789+
.setCreateTimestamp(System.currentTimeMillis())
1790+
.build();
1791+
1792+
EntityResult result =
1793+
metaStoreManager.createEntityIfNotExists(
1794+
getCurrentPolarisContext(),
1795+
PolarisEntity.toCoreList(completePath),
1796+
syntheticNamespace);
1797+
1798+
if (result.isSuccess()) {
1799+
syntheticNamespace = PolarisEntity.of(result.getEntity());
1800+
} else {
1801+
Namespace partialNamespace = Namespace.of(Arrays.copyOf(allNamespaceLevels, i + 1));
1802+
PolarisResolvedPathWrapper partialPath =
1803+
resolutionManifest.getResolvedPath(partialNamespace);
1804+
PolarisEntity partialLeafEntity = partialPath.getRawLeafEntity();
1805+
if (partialLeafEntity == null
1806+
|| !(partialLeafEntity.getName().equals(namespacePart)
1807+
&& partialLeafEntity.getType() == PolarisEntityType.NAMESPACE)) {
1808+
throw new RuntimeException(
1809+
String.format(
1810+
"Failed to create or find namespace entity '%s' in federated catalog '%s'",
1811+
namespacePart, catalogEntity.getName()));
1812+
}
1813+
syntheticNamespace = partialLeafEntity;
1814+
}
1815+
completePath.add(syntheticNamespace);
1816+
currentParent = syntheticNamespace;
1817+
}
1818+
PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest.getResolvedPath(namespace);
1819+
return resolvedPathWrapper;
1820+
}
1821+
17331822
public boolean grantPrivilegeOnTableToRole(
17341823
String catalogName,
17351824
String catalogRoleName,
@@ -2007,9 +2096,9 @@ private boolean grantPrivilegeOnTableLikeToRole(
20072096
TableIdentifier identifier,
20082097
List<PolarisEntitySubType> subTypes,
20092098
PolarisPrivilege privilege) {
2010-
if (findCatalogByName(catalogName).isEmpty()) {
2011-
throw new NotFoundException("Parent catalog %s not found", catalogName);
2012-
}
2099+
CatalogEntity catalogEntity =
2100+
findCatalogByName(catalogName)
2101+
.orElseThrow(() -> new NotFoundException("Parent catalog %s not found", catalogName));
20132102
PolarisEntity catalogRoleEntity =
20142103
findCatalogRoleByName(catalogName, catalogRoleName)
20152104
.orElseThrow(() -> new NotFoundException("CatalogRole %s not found", catalogRoleName));
@@ -2019,7 +2108,20 @@ private boolean grantPrivilegeOnTableLikeToRole(
20192108
identifier, PolarisEntityType.TABLE_LIKE, PolarisEntitySubType.ANY_SUBTYPE);
20202109
if (resolvedPathWrapper == null
20212110
|| !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
2022-
CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier, subTypes);
2111+
if (resolutionManifest.getIsPassthroughFacade()) {
2112+
resolvedPathWrapper =
2113+
createSyntheticTableLikeEntities(
2114+
catalogEntity, identifier, subTypes, resolvedPathWrapper);
2115+
if (resolvedPathWrapper == null
2116+
|| !subTypes.contains(resolvedPathWrapper.getRawLeafEntity().getSubType())) {
2117+
throw new RuntimeException(
2118+
String.format(
2119+
"Failed to create synthetic table-like entity for table %s in catalog %s",
2120+
identifier.name(), catalogEntity.getName()));
2121+
}
2122+
} else {
2123+
CatalogHandler.throwNotFoundExceptionForTableLikeEntity(identifier, subTypes);
2124+
}
20232125
}
20242126
List<PolarisEntity> catalogPath = resolvedPathWrapper.getRawParentPath();
20252127
PolarisEntity tableLikeEntity = resolvedPathWrapper.getRawLeafEntity();
@@ -2034,6 +2136,74 @@ private boolean grantPrivilegeOnTableLikeToRole(
20342136
.isSuccess();
20352137
}
20362138

2139+
/**
2140+
* Creates and persists the missing synthetic table-like entity and its parent namespace entities
2141+
* for external catalogs.
2142+
*
2143+
* @param catalogEntity the external passthrough facade catalog entity.
2144+
* @param identifier the path of the table-like entity(including the namespace).
2145+
* @param subTypes the expected subtypes of the table-like entity
2146+
* @param existingPathWrapper the partially resolved path currently stored in the metastore.
2147+
* @return the resolved path wrapper
2148+
*/
2149+
private PolarisResolvedPathWrapper createSyntheticTableLikeEntities(
2150+
CatalogEntity catalogEntity,
2151+
TableIdentifier identifier,
2152+
List<PolarisEntitySubType> subTypes,
2153+
PolarisResolvedPathWrapper existingPathWrapper) {
2154+
2155+
Namespace namespace = identifier.namespace();
2156+
PolarisResolvedPathWrapper resolvedNamespacePathWrapper =
2157+
!namespace.isEmpty()
2158+
? createSyntheticNamespaceEntities(catalogEntity, namespace, existingPathWrapper)
2159+
: existingPathWrapper;
2160+
2161+
if (resolvedNamespacePathWrapper == null
2162+
|| (!namespace.isEmpty()
2163+
&& !resolvedNamespacePathWrapper.isFullyResolvedNamespace(
2164+
catalogEntity.getName(), namespace))) {
2165+
throw new RuntimeException(
2166+
String.format(
2167+
"Failed to create synthetic namespace entities for namespace %s in catalog %s",
2168+
namespace.toString(), catalogEntity.getName()));
2169+
}
2170+
2171+
// TODO: Instead of creating a synthetic table-like entity, rely on external catalog mediated
2172+
// backfill.
2173+
PolarisEntity parentNamespaceEntity = resolvedNamespacePathWrapper.getRawLeafEntity();
2174+
PolarisEntity syntheticTableEntity =
2175+
new PolarisEntity.Builder()
2176+
.setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
2177+
.setCatalogId(parentNamespaceEntity.getCatalogId())
2178+
.setParentId(parentNamespaceEntity.getId())
2179+
.setType(PolarisEntityType.TABLE_LIKE)
2180+
.setSubType(subTypes.get(0))
2181+
.setName(identifier.name())
2182+
.setCreateTimestamp(System.currentTimeMillis())
2183+
.build();
2184+
2185+
EntityResult result =
2186+
metaStoreManager.createEntityIfNotExists(
2187+
getCurrentPolarisContext(),
2188+
PolarisEntity.toCoreList(resolvedNamespacePathWrapper.getRawFullPath()),
2189+
syntheticTableEntity);
2190+
2191+
if (result.isSuccess()) {
2192+
syntheticTableEntity = PolarisEntity.of(result.getEntity());
2193+
} else {
2194+
PolarisResolvedPathWrapper tablePathWrapper = resolutionManifest.getResolvedPath(identifier);
2195+
PolarisEntity leafEntity =
2196+
tablePathWrapper != null ? tablePathWrapper.getRawLeafEntity() : null;
2197+
if (leafEntity == null || !subTypes.contains(leafEntity.getSubType())) {
2198+
throw new RuntimeException(
2199+
String.format(
2200+
"Failed to create or find table entity '%s' in federated catalog '%s'",
2201+
identifier.name(), catalogEntity.getName()));
2202+
}
2203+
}
2204+
return resolutionManifest.getResolvedPath(identifier);
2205+
}
2206+
20372207
/**
20382208
* Removes a table-level or view-level grant on {@code identifier} from {@code catalogRoleName}.
20392209
*/

0 commit comments

Comments
 (0)