diff --git a/dropwizard/service/src/main/java/org/apache/polaris/service/dropwizard/PolarisApplication.java b/dropwizard/service/src/main/java/org/apache/polaris/service/dropwizard/PolarisApplication.java index 29afa31a7a..29b47a3ebb 100644 --- a/dropwizard/service/src/main/java/org/apache/polaris/service/dropwizard/PolarisApplication.java +++ b/dropwizard/service/src/main/java/org/apache/polaris/service/dropwizard/PolarisApplication.java @@ -60,6 +60,7 @@ import io.opentelemetry.semconv.ServiceAttributes; import io.prometheus.metrics.exporter.servlet.jakarta.PrometheusMetricsServlet; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.inject.Provider; import jakarta.inject.Singleton; import jakarta.servlet.DispatcherType; @@ -93,6 +94,7 @@ import org.apache.polaris.core.persistence.MetaStoreManagerFactory; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.persistence.cache.EntityCacheGrantManager; import org.apache.polaris.core.persistence.cache.PolarisRemoteCache; import org.apache.polaris.service.admin.PolarisServiceImpl; import org.apache.polaris.service.admin.api.PolarisCatalogsApi; @@ -302,7 +304,12 @@ protected void configure() { // manager bindFactory(PolarisMetaStoreManagerFactory.class) .in(RealmScoped.class) + .named("delegateGrantManager") .to(PolarisGrantManager.class); + bindFactory(EntityCacheGrantManagerFactory.class) + .in(RealmScoped.class) + .to(PolarisGrantManager.class) + .ranked(100); polarisMetricRegistry.init( IcebergRestCatalogApi.class, IcebergRestConfigurationApi.class, @@ -527,6 +534,23 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } } + private static class EntityCacheGrantManagerFactory implements Factory { + @Inject + @Named("delegateGrantManager") + PolarisGrantManager grantManager; + + @Inject EntityCache entityCache; + + @RealmScoped + @Override + public PolarisGrantManager provide() { + return new EntityCacheGrantManager(grantManager, entityCache); + } + + @Override + public void dispose(PolarisGrantManager instance) {} + } + /** Factory to create a CallContext based on the request contents. */ @RequestScoped private static class CallContextFactory implements Factory { diff --git a/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/admin/PolarisAuthzTestBase.java b/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/admin/PolarisAuthzTestBase.java index 58a2e6ee16..575d4300c4 100644 --- a/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/admin/PolarisAuthzTestBase.java +++ b/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/admin/PolarisAuthzTestBase.java @@ -65,6 +65,7 @@ import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.persistence.cache.EntityCacheGrantManager; import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.service.admin.PolarisAdminService; @@ -132,12 +133,7 @@ public abstract class PolarisAuthzTestBase { new Schema( required(3, "id", Types.IntegerType.get(), "unique ID 🤪"), required(4, "data", Types.StringType.get())); - protected final PolarisAuthorizer polarisAuthorizer = - new PolarisAuthorizerImpl( - new DefaultConfigurationStore( - Map.of( - PolarisConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING.key, - true))); + protected PolarisAuthorizer polarisAuthorizer; protected BasePolarisCatalog baseCatalog; protected PolarisAdminService adminService; @@ -164,6 +160,7 @@ public void before() { Map configMap = Map.of( "ALLOW_SPECIFYING_FILE_IO_IMPL", true, "ALLOW_EXTERNAL_METADATA_FILE_LOCATION", true); + PolarisCallContext polarisContext = new PolarisCallContext( metaStoreManagerFactory.getOrCreateSessionSupplier(realmContext).get(), @@ -183,6 +180,19 @@ public void before() { callContext = CallContext.of(realmContext, polarisContext); CallContext.setCurrentContext(callContext); + EntityCache entityCache = new EntityCache(metaStoreManager); + polarisAuthorizer = + new PolarisAuthorizerImpl( + new DefaultConfigurationStore( + Map.of( + PolarisConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING + .key, + true)), + () -> new EntityCacheGrantManager(metaStoreManager, entityCache)); + this.entityManager = + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache); + this.metaStoreManager = metaStoreManager; + PrincipalEntity rootEntity = new PrincipalEntity( PolarisEntity.of( diff --git a/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogTest.java b/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogTest.java index 1b7c59e228..4c09fa2e05 100644 --- a/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogTest.java +++ b/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogTest.java @@ -81,6 +81,7 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.PolarisMetaStoreSession; import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.persistence.cache.EntityCacheGrantManager; import org.apache.polaris.core.storage.PolarisCredentialProperty; import org.apache.polaris.core.storage.PolarisStorageIntegration; import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider; @@ -158,9 +159,9 @@ public void before() { } }, Clock.systemDefaultZone()); + EntityCache entityCache = new EntityCache(metaStoreManager); entityManager = - new PolarisEntityManager( - metaStoreManager, new StorageCredentialCache(), new EntityCache(metaStoreManager)); + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache); CallContext callContext = CallContext.of(realmContext, polarisContext); CallContext.setCurrentContext(callContext); @@ -185,7 +186,10 @@ public void before() { entityManager, metaStoreManager, authenticatedRoot, - new PolarisAuthorizerImpl(new PolarisConfigurationStore() {})); + new PolarisAuthorizerImpl( + new PolarisConfigurationStore() {}, + () -> new EntityCacheGrantManager(metaStoreManager, entityCache))); + String storageLocation = "s3://my-bucket/path/to/data"; storageConfigModel = AwsStorageConfigInfo.builder() diff --git a/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogViewTest.java b/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogViewTest.java index 17f1cf0197..967e5b383d 100644 --- a/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogViewTest.java +++ b/dropwizard/service/src/test/java/org/apache/polaris/service/dropwizard/catalog/BasePolarisCatalogViewTest.java @@ -50,6 +50,7 @@ import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.persistence.cache.EntityCacheGrantManager; import org.apache.polaris.core.storage.cache.StorageCredentialCache; import org.apache.polaris.service.admin.PolarisAdminService; import org.apache.polaris.service.catalog.BasePolarisCatalog; @@ -90,9 +91,9 @@ public void before() { }, Clock.systemDefaultZone()); + EntityCache entityCache = new EntityCache(metaStoreManager); PolarisEntityManager entityManager = - new PolarisEntityManager( - metaStoreManager, new StorageCredentialCache(), new EntityCache(metaStoreManager)); + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache); CallContext callContext = CallContext.of(null, polarisContext); CallContext.setCurrentContext(callContext); @@ -117,7 +118,9 @@ public void before() { entityManager, metaStoreManager, authenticatedRoot, - new PolarisAuthorizerImpl(new PolarisConfigurationStore() {})); + new PolarisAuthorizerImpl( + new PolarisConfigurationStore() {}, + () -> new EntityCacheGrantManager(metaStoreManager, entityCache))); adminService.createCatalog( new CatalogEntity.Builder() .setName(CATALOG_NAME) diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java index 96f449acbd..2bf37be553 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisAuthorizerImpl.java @@ -94,20 +94,22 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import jakarta.inject.Inject; +import jakarta.inject.Provider; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.apache.iceberg.exceptions.ForbiddenException; +import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.PolarisConfiguration; import org.apache.polaris.core.PolarisConfigurationStore; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntity; import org.apache.polaris.core.entity.PolarisEntityConstants; import org.apache.polaris.core.entity.PolarisEntityCore; import org.apache.polaris.core.entity.PolarisGrantRecord; import org.apache.polaris.core.entity.PolarisPrivilege; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; -import org.apache.polaris.core.persistence.ResolvedPolarisEntity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -460,10 +462,13 @@ public class PolarisAuthorizerImpl implements PolarisAuthorizer { } private final PolarisConfigurationStore featureConfig; + private final Provider grantManagerFactory; @Inject - public PolarisAuthorizerImpl(PolarisConfigurationStore featureConfig) { + public PolarisAuthorizerImpl( + PolarisConfigurationStore featureConfig, Provider grantManagerFactory) { this.featureConfig = featureConfig; + this.grantManagerFactory = grantManagerFactory; } /** @@ -604,15 +609,23 @@ public boolean hasTransitivePrivilege( Set activatedGranteeIds, PolarisPrivilege desiredPrivilege, PolarisResolvedPathWrapper resolvedPath) { + PolarisGrantManager grantManager = grantManagerFactory.get(); + PolarisCallContext callContext = CallContext.getCurrentContext().getPolarisCallContext(); // Iterate starting at the parent, since the most common case should be to manage grants as // high up in the resource hierarchy as possible, so we expect earlier termination. - for (ResolvedPolarisEntity resolvedSecurableEntity : resolvedPath.getResolvedFullPath()) { - Preconditions.checkState( - resolvedSecurableEntity.getGrantRecordsAsSecurable() != null, - "Got null grantRecordsAsSecurable for resolvedSecurableEntity %s", - resolvedSecurableEntity); - for (PolarisGrantRecord grantRecord : resolvedSecurableEntity.getGrantRecordsAsSecurable()) { + for (PolarisEntity resolvedSecurableEntity : resolvedPath.getRawFullPath()) { + PolarisGrantManager.LoadGrantsResult grantsResult = + grantManager.loadGrantsOnSecurable( + callContext, resolvedSecurableEntity.getCatalogId(), resolvedSecurableEntity.getId()); + callContext + .getDiagServices() + .check( + grantsResult.isSuccess(), + "no_grants_for_securable", + "unable to load grants for securable %s", + resolvedSecurableEntity.getId()); + for (PolarisGrantRecord grantRecord : grantsResult.getGrantRecords()) { if (matchesOrIsSubsumedBy( desiredPrivilege, PolarisPrivilege.fromCode(grantRecord.getPrivilegeCode()))) { // Found a potential candidate for satisfying our authz goal. diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java index 1cf52142a8..178a9a06f3 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisGrantManager.java @@ -35,6 +35,7 @@ /** Manage grants for Polaris entities. */ public interface PolarisGrantManager { + /** * Grant usage on a role to a grantee, for example granting usage on a catalog role to a principal * role or granting a principal role to a principal. diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisEntityManager.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisEntityManager.java index 3d0d2457a5..a331379e4b 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisEntityManager.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisEntityManager.java @@ -20,15 +20,10 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import java.util.List; import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.PolarisEntity; -import org.apache.polaris.core.entity.PolarisEntityConstants; -import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; -import org.apache.polaris.core.entity.PolarisGrantRecord; -import org.apache.polaris.core.entity.PolarisPrivilege; import org.apache.polaris.core.persistence.cache.EntityCache; import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; import org.apache.polaris.core.persistence.resolver.Resolver; @@ -45,8 +40,18 @@ public class PolarisEntityManager { private final StorageCredentialCache credentialCache; - // Lazily instantiated only a single time per entity manager. - private ResolvedPolarisEntity implicitResolvedRootContainerEntity = null; + // For now, the root container is only implicit and doesn't exist in the entity store, and only + // the service_admin PrincipalRole has the SERVICE_MANAGE_ACCESS grant on this entity. If it + // becomes possible to grant other PrincipalRoles with SERVICE_MANAGE_ACCESS or other privileges + // on this root entity, then we must actually create a representation of this root entity in the + // entity store itself. + private final PolarisEntity implicitResolvedRootContainerEntity = + new PolarisEntity.Builder() + .setId(0L) + .setCatalogId(0L) + .setType(PolarisEntityType.ROOT) + .setName("root") + .build(); /** * @param metaStoreManager the metastore manager for the current realm @@ -85,60 +90,10 @@ public PolarisResolutionManifest prepareResolutionManifest( PolarisResolutionManifest manifest = new PolarisResolutionManifest( callContext, this, authenticatedPrincipal, referenceCatalogName); - manifest.setSimulatedResolvedRootContainerEntity( - getSimulatedResolvedRootContainerEntity(callContext)); + manifest.setSimulatedResolvedRootContainerEntity(implicitResolvedRootContainerEntity); return manifest; } - /** - * Returns a ResolvedPolarisEntity representing the realm-level "root" entity that is the implicit - * parent container of all things in this realm. - */ - private synchronized ResolvedPolarisEntity getSimulatedResolvedRootContainerEntity( - CallContext callContext) { - if (implicitResolvedRootContainerEntity == null) { - // For now, the root container is only implicit and doesn't exist in the entity store, and - // only - // the service_admin PrincipalRole has the SERVICE_MANAGE_ACCESS grant on this entity. If it - // becomes - // possible to grant other PrincipalRoles with SERVICE_MANAGE_ACCESS or other privileges on - // this - // root entity, then we must actually create a representation of this root entity in the - // entity store itself. - PolarisEntity serviceAdminPrincipalRole = - PolarisEntity.of( - metaStoreManager - .readEntityByName( - callContext.getPolarisCallContext(), - null, - PolarisEntityType.PRINCIPAL_ROLE, - PolarisEntitySubType.NULL_SUBTYPE, - PolarisEntityConstants.getNameOfPrincipalServiceAdminRole()) - .getEntity()); - if (serviceAdminPrincipalRole == null) { - throw new IllegalStateException("Failed to resolve service_admin PrincipalRole"); - } - PolarisEntity rootContainerEntity = - new PolarisEntity.Builder() - .setId(0L) - .setCatalogId(0L) - .setType(PolarisEntityType.ROOT) - .setName("root") - .build(); - PolarisGrantRecord serviceAdminGrant = - new PolarisGrantRecord( - 0L, - 0L, - serviceAdminPrincipalRole.getCatalogId(), - serviceAdminPrincipalRole.getId(), - PolarisPrivilege.SERVICE_MANAGE_ACCESS.getCode()); - - implicitResolvedRootContainerEntity = - new ResolvedPolarisEntity(rootContainerEntity, null, List.of(serviceAdminGrant)); - } - return implicitResolvedRootContainerEntity; - } - public StorageCredentialCache getCredentialCache() { return credentialCache; } diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisResolvedPathWrapper.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisResolvedPathWrapper.java index 6b09598c45..bdf7f32c43 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisResolvedPathWrapper.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/PolarisResolvedPathWrapper.java @@ -19,7 +19,6 @@ package org.apache.polaris.core.persistence; import java.util.List; -import java.util.stream.Collectors; import org.apache.polaris.core.entity.PolarisEntity; /** @@ -27,15 +26,15 @@ * and grant records. */ public class PolarisResolvedPathWrapper { - private final List resolvedPath; + private final List resolvedPath; // TODO: Distinguish between whether parentPath had a null in the chain or whether only // the leaf element was null. - public PolarisResolvedPathWrapper(List resolvedPath) { + public PolarisResolvedPathWrapper(List resolvedPath) { this.resolvedPath = resolvedPath; } - public ResolvedPolarisEntity getResolvedLeafEntity() { + public PolarisEntity getResolvedLeafEntity() { if (resolvedPath == null || resolvedPath.isEmpty()) { return null; } @@ -43,38 +42,18 @@ public ResolvedPolarisEntity getResolvedLeafEntity() { } public PolarisEntity getRawLeafEntity() { - ResolvedPolarisEntity resolvedEntity = getResolvedLeafEntity(); - if (resolvedEntity != null) { - return resolvedEntity.getEntity(); - } - return null; - } - - public List getResolvedFullPath() { - return resolvedPath; + return getResolvedLeafEntity(); } public List getRawFullPath() { - if (resolvedPath == null) { - return null; - } - return resolvedPath.stream().map(ResolvedPolarisEntity::getEntity).collect(Collectors.toList()); - } - - public List getResolvedParentPath() { - if (resolvedPath == null) { - return null; - } - return resolvedPath.subList(0, resolvedPath.size() - 1); + return resolvedPath; } public List getRawParentPath() { if (resolvedPath == null) { return null; } - return getResolvedParentPath().stream() - .map(ResolvedPolarisEntity::getEntity) - .collect(Collectors.toList()); + return resolvedPath.subList(0, resolvedPath.size() - 1); } @Override diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/ResolvedPolarisEntity.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/ResolvedPolarisEntity.java deleted file mode 100644 index a091362ac2..0000000000 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/ResolvedPolarisEntity.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.polaris.core.persistence; - -import com.google.common.collect.ImmutableList; -import jakarta.annotation.Nonnull; -import java.util.List; -import org.apache.polaris.core.entity.PolarisEntity; -import org.apache.polaris.core.entity.PolarisGrantRecord; -import org.apache.polaris.core.persistence.cache.EntityCacheEntry; - -public class ResolvedPolarisEntity { - private final PolarisEntity entity; - - // only non-empty if this entity can be a grantee; these are the grants on other - // roles/securables granted to this entity. - private final List grantRecordsAsGrantee; - - // grants associated to this entity as the securable; for a principal role or catalog role - // these may be ROLE_USAGE or other permission-management privileges. For a catalog securable, - // these are the grants like TABLE_READ_PROPERTIES, NAMESPACE_LIST, etc. - private final List grantRecordsAsSecurable; - - public ResolvedPolarisEntity( - PolarisEntity entity, - List grantRecordsAsGrantee, - List grantRecordsAsSecurable) { - this.entity = entity; - // TODO: Precondition checks that grantee or securable ids in grant records match entity as - // expected. - this.grantRecordsAsGrantee = grantRecordsAsGrantee; - this.grantRecordsAsSecurable = grantRecordsAsSecurable; - } - - public ResolvedPolarisEntity(@Nonnull EntityCacheEntry cacheEntry) { - this.entity = PolarisEntity.of(cacheEntry.getEntity()); - this.grantRecordsAsGrantee = ImmutableList.copyOf(cacheEntry.getGrantRecordsAsGrantee()); - this.grantRecordsAsSecurable = ImmutableList.copyOf(cacheEntry.getGrantRecordsAsSecurable()); - } - - public PolarisEntity getEntity() { - return entity; - } - - /** The grant records associated with this entity being the grantee of the record. */ - public List getGrantRecordsAsGrantee() { - return grantRecordsAsGrantee; - } - - /** The grant records associated with this entity being the securable of the record. */ - public List getGrantRecordsAsSecurable() { - return grantRecordsAsSecurable; - } - - @Override - public String toString() { - return "entity:" - + entity - + ";grantRecordsAsGrantee:" - + grantRecordsAsGrantee - + ";grantRecordsAsSecurable:" - + grantRecordsAsSecurable; - } -} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java index 8836a171ca..1cfbfb8ce8 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCache.java @@ -24,6 +24,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; import java.util.AbstractMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -31,6 +32,7 @@ import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.context.RealmScoped; import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntityCore; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PolarisGrantRecord; import org.apache.polaris.core.persistence.cache.PolarisRemoteCache.CachedEntryResult; @@ -106,6 +108,13 @@ public void removeCacheEntry(@Nonnull EntityCacheEntry cacheEntry) { this.byName.remove(nameKey, cacheEntry); } + public void removeCacheEntry(@NotNull PolarisEntityCore entityCore) { + EntityCacheEntry cacheEntry = this.getEntityById(entityCore.getId()); + if (cacheEntry != null) { + this.removeCacheEntry(cacheEntry); + } + } + /** * Cache new entry * diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCacheGrantManager.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCacheGrantManager.java new file mode 100644 index 0000000000..2cddcb303a --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/EntityCacheGrantManager.java @@ -0,0 +1,220 @@ +/* + * 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.polaris.core.persistence.cache; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.auth.PolarisGrantManager; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntityConstants; +import org.apache.polaris.core.entity.PolarisEntityCore; +import org.apache.polaris.core.entity.PolarisEntityType; +import org.apache.polaris.core.entity.PolarisGrantRecord; +import org.apache.polaris.core.entity.PolarisPrivilege; +import org.apache.polaris.core.persistence.BaseResult; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * PolarisGrantManger implementation that uses an EntityCache to retrieve entities and is backed by + * a delegate grant manager for persisting grant changes. This allows consumers to reuse cache + * entities without necessarily being aware of the {@link EntityCache} or the {@link + * EntityCacheEntry} specifics. Typically, the {@link + * org.apache.polaris.core.persistence.resolver.Resolver} is responsible for validating the cache + * state. This class does no validation of the entity version or the grant version of any cache + * record. + */ +public class EntityCacheGrantManager implements PolarisGrantManager { + private static final Logger LOGGER = LoggerFactory.getLogger(EntityCacheGrantManager.class); + private final PolarisGrantManager delegateGrantManager; + private final EntityCache entityCache; + private PolarisGrantRecord serviceAdminRootContainerGrant; + private PolarisBaseEntity serviceAdminEntity; + + public EntityCacheGrantManager( + PolarisGrantManager delegateGrantManager, EntityCache entityCache) { + this.delegateGrantManager = delegateGrantManager; + this.entityCache = entityCache; + } + + @Override + public @NotNull PrivilegeResult grantUsageOnRoleToGrantee( + @NotNull PolarisCallContext callCtx, + @Nullable PolarisEntityCore catalog, + @NotNull PolarisEntityCore role, + @NotNull PolarisEntityCore grantee) { + try { + return delegateGrantManager.grantUsageOnRoleToGrantee(callCtx, catalog, role, grantee); + } finally { + LOGGER.debug("Invalidating cache for role {} and grantee {}", role, grantee); + entityCache.removeCacheEntry(role); + entityCache.removeCacheEntry(grantee); + } + } + + @Override + public @NotNull PrivilegeResult revokeUsageOnRoleFromGrantee( + @NotNull PolarisCallContext callCtx, + @Nullable PolarisEntityCore catalog, + @NotNull PolarisEntityCore role, + @NotNull PolarisEntityCore grantee) { + try { + return delegateGrantManager.revokeUsageOnRoleFromGrantee(callCtx, catalog, role, grantee); + } finally { + LOGGER.debug("Invalidating cache for role {} and grantee {}", role, grantee); + entityCache.removeCacheEntry(role); + entityCache.removeCacheEntry(grantee); + } + } + + @Override + public @NotNull PrivilegeResult grantPrivilegeOnSecurableToRole( + @NotNull PolarisCallContext callCtx, + @NotNull PolarisEntityCore grantee, + @Nullable List catalogPath, + @NotNull PolarisEntityCore securable, + @NotNull PolarisPrivilege privilege) { + try { + return delegateGrantManager.grantPrivilegeOnSecurableToRole( + callCtx, grantee, catalogPath, securable, privilege); + } finally { + LOGGER.debug("Invalidating cache for securable {} and grantee {}", securable, grantee); + entityCache.removeCacheEntry(securable); + entityCache.removeCacheEntry(grantee); + } + } + + @Override + public @NotNull PrivilegeResult revokePrivilegeOnSecurableFromRole( + @NotNull PolarisCallContext callCtx, + @NotNull PolarisEntityCore grantee, + @Nullable List catalogPath, + @NotNull PolarisEntityCore securable, + @NotNull PolarisPrivilege privilege) { + try { + return delegateGrantManager.revokePrivilegeOnSecurableFromRole( + callCtx, grantee, catalogPath, securable, privilege); + } finally { + LOGGER.debug("Invalidating cache for securable {} and grantee {}", securable, grantee); + entityCache.removeCacheEntry(securable); + entityCache.removeCacheEntry(grantee); + } + } + + @Override + public @NotNull LoadGrantsResult loadGrantsOnSecurable( + @NotNull PolarisCallContext callCtx, long securableCatalogId, long securableId) { + EntityCacheLookupResult lookupResult = + entityCache.getOrLoadEntityById(callCtx, securableCatalogId, securableId); + if (lookupResult == null || lookupResult.getCacheEntry() == null) { + return new LoadGrantsResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); + } + List grantRecords = + lookupResult.getCacheEntry().getGrantRecordsAsSecurable(); + List granteeList = new ArrayList<>(); + grantRecords.stream() + .map( + gr -> + entityCache.getOrLoadEntityById( + callCtx, gr.getGranteeCatalogId(), gr.getGranteeId())) + .filter(lr -> lr != null && lr.getCacheEntry() != null) + .map(lr -> lr.getCacheEntry().getEntity()) + .forEach(granteeList::add); + PolarisBaseEntity entity = lookupResult.getCacheEntry().getEntity(); + if (granteeList.size() != lookupResult.getCacheEntry().getGrantRecordsAsSecurable().size()) { + LOGGER.error("Failed to resolve all grantees for securable {}", entity); + return new LoadGrantsResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); + } + + // If the securable is the root container, then we need to add a grant record for the + // service_admin PrincipalRole, which is the only role that has the SERVICE_MANAGE_ACCESS + // privilege on the root + if (entity.getName().equals(PolarisEntityConstants.getRootContainerName()) + && entity.getType().equals(PolarisEntityType.ROOT)) { + if (serviceAdminEntity == null || serviceAdminRootContainerGrant == null) { + EntityCacheLookupResult serviceAdminRole = + entityCache.getOrLoadEntityByName( + callCtx, + new EntityCacheByNameKey( + 0L, + 0L, + PolarisEntityType.PRINCIPAL_ROLE, + PolarisEntityConstants.getNameOfPrincipalServiceAdminRole())); + if (serviceAdminRole == null || serviceAdminRole.getCacheEntry() == null) { + LOGGER.error("Failed to resolve service_admin PrincipalRole"); + return new LoadGrantsResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); + } + serviceAdminRootContainerGrant = + new PolarisGrantRecord( + 0L, + 0L, + serviceAdminRole.getCacheEntry().getEntity().getCatalogId(), + serviceAdminRole.getCacheEntry().getEntity().getId(), + PolarisPrivilege.SERVICE_MANAGE_ACCESS.getCode()); + serviceAdminEntity = serviceAdminRole.getCacheEntry().getEntity(); + } + grantRecords.stream() + .filter(gr -> gr.getGranteeId() == serviceAdminEntity.getId()) + .findFirst() + .ifPresentOrElse( + gr -> { + LOGGER.trace("service_admin PrincipalRole already has a grant record on the root"); + }, + () -> { + LOGGER.trace("Adding service_admin PrincipalRole grant record on the root"); + grantRecords.add(serviceAdminRootContainerGrant); + granteeList.add(serviceAdminEntity); + }); + } + return new LoadGrantsResult(entity.getGrantRecordsVersion(), grantRecords, granteeList); + } + + @Override + public @NotNull LoadGrantsResult loadGrantsToGrantee( + PolarisCallContext callCtx, long granteeCatalogId, long granteeId) { + EntityCacheLookupResult lookupResult = + entityCache.getOrLoadEntityById(callCtx, granteeCatalogId, granteeId); + if (lookupResult == null || lookupResult.getCacheEntry() == null) { + return new LoadGrantsResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); + } + List securableList = + lookupResult.getCacheEntry().getGrantRecordsAsGrantee().stream() + .map( + gr -> + entityCache.getOrLoadEntityById( + callCtx, gr.getSecurableCatalogId(), gr.getSecurableId())) + .filter(lr -> lr != null && lr.getCacheEntry() != null) + .map(lr -> lr.getCacheEntry().getEntity()) + .collect(Collectors.toList()); + if (securableList.size() != lookupResult.getCacheEntry().getGrantRecordsAsGrantee().size()) { + LOGGER.error( + "Failed to resolve all securables for grantee {}", + lookupResult.getCacheEntry().getEntity()); + return new LoadGrantsResult(BaseResult.ReturnStatus.GRANT_NOT_FOUND, null); + } + return new LoadGrantsResult( + lookupResult.getCacheEntry().getEntity().getGrantRecordsVersion(), + lookupResult.getCacheEntry().getGrantRecordsAsGrantee(), + securableList); + } +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/RealmEntityCacheFactory.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/RealmEntityCacheFactory.java new file mode 100644 index 0000000000..f44e16638f --- /dev/null +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/cache/RealmEntityCacheFactory.java @@ -0,0 +1,27 @@ +/* + * 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.polaris.core.persistence.cache; + +import org.apache.polaris.core.context.RealmContext; + +/** Factory interface for finding the {@link EntityCache} for a realm */ +@FunctionalInterface +public interface RealmEntityCacheFactory { + EntityCache getOrCreateEntityCache(RealmContext realm); +} diff --git a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java index 629e282e18..86e119ea32 100644 --- a/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java +++ b/polaris-core/src/main/java/org/apache/polaris/core/persistence/resolver/PolarisResolutionManifest.java @@ -31,13 +31,13 @@ import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntity; import org.apache.polaris.core.entity.PolarisEntityConstants; import org.apache.polaris.core.entity.PolarisEntitySubType; import org.apache.polaris.core.entity.PolarisEntityType; import org.apache.polaris.core.entity.PrincipalRoleEntity; import org.apache.polaris.core.persistence.PolarisEntityManager; import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; -import org.apache.polaris.core.persistence.ResolvedPolarisEntity; import org.apache.polaris.core.persistence.cache.EntityCacheEntry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,7 +71,7 @@ public class PolarisResolutionManifest implements PolarisResolutionManifestCatal // Catalog, Principal, and PrincipalRole. // This simulated entity will be used if the actual resolver fails to resolve the rootContainer // on the backend due to compatibility mismatches. - private ResolvedPolarisEntity simulatedResolvedRootContainerEntity = null; + private PolarisEntity simulatedResolvedRootContainerEntity = null; private int currentPathIndex = 0; @@ -209,16 +209,19 @@ public PolarisResolvedPathWrapper getPassthroughResolvedPath(Object key) { "Returning null for key {} due to size mismatch from getPassthroughResolvedPath " + "resolvedPath: {}, requestedPath.getEntityNames(): {}", key, - resolvedPath.stream().map(ResolvedPolarisEntity::new).collect(Collectors.toList()), + resolvedPath, requestedPath.getEntityNames()); return null; } } - List resolvedEntities = new ArrayList<>(); - resolvedEntities.add( - new ResolvedPolarisEntity(passthroughResolver.getResolvedReferenceCatalog())); - resolvedPath.forEach(cacheEntry -> resolvedEntities.add(new ResolvedPolarisEntity(cacheEntry))); + List resolvedEntities = new ArrayList<>(); + EntityCacheEntry referenceCatalog = passthroughResolver.getResolvedReferenceCatalog(); + if (referenceCatalog != null) { + resolvedEntities.add(PolarisEntity.of(referenceCatalog.getEntity())); + } + resolvedPath.forEach( + cacheEntry -> resolvedEntities.add(PolarisEntity.of(cacheEntry.getEntity()))); LOGGER.debug( "Returning resolvedEntities from getPassthroughResolvedPath: {}", resolvedEntities); return new PolarisResolvedPathWrapper(resolvedEntities); @@ -265,11 +268,11 @@ public Set getAllActivatedPrincipalRoleEntities() { } public void setSimulatedResolvedRootContainerEntity( - ResolvedPolarisEntity simulatedResolvedRootContainerEntity) { + PolarisEntity simulatedResolvedRootContainerEntity) { this.simulatedResolvedRootContainerEntity = simulatedResolvedRootContainerEntity; } - private ResolvedPolarisEntity getResolvedRootContainerEntity() { + private PolarisEntity getResolvedRootContainerEntity() { if (primaryResolverStatus.getStatus() != ResolverStatus.StatusEnum.SUCCESS) { return null; } @@ -280,7 +283,7 @@ private ResolvedPolarisEntity getResolvedRootContainerEntity() { LOGGER.debug("Failed to find rootContainer, so using simulated rootContainer instead."); return simulatedResolvedRootContainerEntity; } - return new ResolvedPolarisEntity(resolvedCacheEntry); + return PolarisEntity.of(resolvedCacheEntry.getEntity()); } public PolarisResolvedPathWrapper getResolvedRootContainerEntityAsPath() { @@ -300,14 +303,13 @@ public PolarisResolvedPathWrapper getResolvedReferenceCatalogEntity( if (prependRootContainer) { // Operations directly on Catalogs also consider the root container to be a parent of its // authorization chain. - // TODO: Throw appropriate Catalog NOT_FOUND exception before any call to - // getResolvedReferenceCatalogEntity(). return new PolarisResolvedPathWrapper( List.of( - getResolvedRootContainerEntity(), new ResolvedPolarisEntity(resolvedCachedCatalog))); + getResolvedRootContainerEntity(), + PolarisEntity.of(resolvedCachedCatalog.getEntity()))); } else { return new PolarisResolvedPathWrapper( - List.of(new ResolvedPolarisEntity(resolvedCachedCatalog))); + List.of(PolarisEntity.of(resolvedCachedCatalog.getEntity()))); } } @@ -355,12 +357,16 @@ public PolarisResolvedPathWrapper getResolvedPath(Object key, boolean prependRoo } } - List resolvedEntities = new ArrayList<>(); + List resolvedEntities = new ArrayList<>(); if (prependRootContainer) { resolvedEntities.add(getResolvedRootContainerEntity()); } - resolvedEntities.add(new ResolvedPolarisEntity(primaryResolver.getResolvedReferenceCatalog())); - resolvedPath.forEach(cacheEntry -> resolvedEntities.add(new ResolvedPolarisEntity(cacheEntry))); + EntityCacheEntry referenceCatalog = primaryResolver.getResolvedReferenceCatalog(); + if (referenceCatalog != null) { + resolvedEntities.add(PolarisEntity.of(referenceCatalog.getEntity())); + } + resolvedPath.forEach( + cacheEntry -> resolvedEntities.add(PolarisEntity.of(cacheEntry.getEntity()))); return new PolarisResolvedPathWrapper(resolvedEntities); } @@ -403,10 +409,10 @@ public PolarisResolvedPathWrapper getResolvedTopLevelEntity( return null; } - ResolvedPolarisEntity resolvedRootContainerEntity = getResolvedRootContainerEntity(); + PolarisEntity resolvedRootContainerEntity = getResolvedRootContainerEntity(); return resolvedRootContainerEntity == null - ? new PolarisResolvedPathWrapper(List.of(new ResolvedPolarisEntity(resolvedCacheEntry))) + ? new PolarisResolvedPathWrapper(List.of(PolarisEntity.of(resolvedCacheEntry.getEntity()))) : new PolarisResolvedPathWrapper( - List.of(resolvedRootContainerEntity, new ResolvedPolarisEntity(resolvedCacheEntry))); + List.of(resolvedRootContainerEntity, PolarisEntity.of(resolvedCacheEntry.getEntity()))); } } diff --git a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/EntityCacheGrantManagerTest.java b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/EntityCacheGrantManagerTest.java new file mode 100644 index 0000000000..ad482db0da --- /dev/null +++ b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/EntityCacheGrantManagerTest.java @@ -0,0 +1,512 @@ +/* + * 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.polaris.core.storage.cache; + +import static org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RANDOM_SECRETS; + +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.iceberg.catalog.Namespace; +import org.apache.iceberg.catalog.TableIdentifier; +import org.apache.polaris.core.PolarisCallContext; +import org.apache.polaris.core.PolarisDefaultDiagServiceImpl; +import org.apache.polaris.core.PolarisDiagnostics; +import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal; +import org.apache.polaris.core.auth.PolarisGrantManager; +import org.apache.polaris.core.context.CallContext; +import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.core.entity.CatalogEntity; +import org.apache.polaris.core.entity.CatalogRoleEntity; +import org.apache.polaris.core.entity.NamespaceEntity; +import org.apache.polaris.core.entity.PolarisBaseEntity; +import org.apache.polaris.core.entity.PolarisEntity; +import org.apache.polaris.core.entity.PolarisEntityConstants; +import org.apache.polaris.core.entity.PolarisEntityCore; +import org.apache.polaris.core.entity.PolarisEntitySubType; +import org.apache.polaris.core.entity.PolarisEntityType; +import org.apache.polaris.core.entity.PolarisGrantRecord; +import org.apache.polaris.core.entity.PolarisPrivilege; +import org.apache.polaris.core.entity.PrincipalEntity; +import org.apache.polaris.core.entity.PrincipalRoleEntity; +import org.apache.polaris.core.entity.TableLikeEntity; +import org.apache.polaris.core.persistence.BaseResult; +import org.apache.polaris.core.persistence.LocalPolarisMetaStoreManagerFactory; +import org.apache.polaris.core.persistence.MetaStoreManagerFactory; +import org.apache.polaris.core.persistence.PolarisEntityManager; +import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.core.persistence.PolarisMetaStoreSession; +import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper; +import org.apache.polaris.core.persistence.PolarisTreeMapMetaStoreSessionImpl; +import org.apache.polaris.core.persistence.PolarisTreeMapStore; +import org.apache.polaris.core.persistence.cache.EntityCache; +import org.apache.polaris.core.persistence.cache.EntityCacheGrantManager; +import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest; +import org.apache.polaris.core.persistence.resolver.ResolverPath; +import org.apache.polaris.core.persistence.resolver.ResolverStatus; +import org.assertj.core.api.Assertions; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class EntityCacheGrantManagerTest { + + public static final String NS_1 = "ns1"; + public static final String NS_2 = "ns2"; + public static final String TABLE_1 = "table1"; + private MetaStoreManagerFactory metastoreManagerFactory; + private RealmContext realmContext; + private PolarisMetaStoreManager metaStoreManager; + private PolarisCallContext polarisCallContext; + private CatalogEntity catalog; + private PrincipalEntity principal; + private PrincipalRoleEntity principalRole1; + private PrincipalRoleEntity principalRole2; + private CatalogRoleEntity catalogRole1; + private CatalogRoleEntity catalogRole2; + + @BeforeEach + public void setup() { + // Create a MetastoreManager + PolarisDefaultDiagServiceImpl diagServices = new PolarisDefaultDiagServiceImpl(); + PolarisTreeMapStore treeMapStore = new PolarisTreeMapStore(diagServices); + PolarisTreeMapMetaStoreSessionImpl session = + new PolarisTreeMapMetaStoreSessionImpl(treeMapStore, Mockito.mock(), RANDOM_SECRETS); + metastoreManagerFactory = + new LocalPolarisMetaStoreManagerFactory() { + + @Override + protected PolarisTreeMapStore createBackingStore( + @NotNull PolarisDiagnostics diagnostics) { + return treeMapStore; + } + + @Override + protected PolarisMetaStoreSession createMetaStoreSession( + @NotNull PolarisTreeMapStore store, @NotNull RealmContext realmContext) { + return session; + } + }; + // Create a Resolver and an EntityCacheGrantManager + realmContext = () -> "realm"; + metastoreManagerFactory.bootstrapRealms(List.of(realmContext.getRealmIdentifier())); + metaStoreManager = metastoreManagerFactory.getOrCreateMetaStoreManager(realmContext); + polarisCallContext = new PolarisCallContext(session, diagServices); + + // Create a principal with three Principal roles + principal = + new PrincipalEntity.Builder() + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setName("principal1") + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build(); + principalRole1 = + new PrincipalRoleEntity.Builder() + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setName("principalRole1") + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build(); + principalRole2 = + new PrincipalRoleEntity.Builder() + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setName("principalRole2") + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build(); + + // Persist the principal and principal roles + metaStoreManager.createPrincipal(polarisCallContext, principal); + metaStoreManager.createEntityIfNotExists(polarisCallContext, null, principalRole1); + metaStoreManager.createEntityIfNotExists(polarisCallContext, null, principalRole2); + + // Create a catalog with two catalog roles + catalog = + new CatalogEntity.Builder() + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setName("mycatalog") + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build(); + catalogRole1 = + new CatalogRoleEntity.Builder() + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setName("mycatrole1") + .setCatalogId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build(); + catalogRole2 = + new CatalogRoleEntity.Builder() + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setName("mycatrole2") + .setCatalogId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build(); + metaStoreManager.createCatalog(polarisCallContext, catalog, List.of(principalRole1)); + metaStoreManager.createEntityIfNotExists(polarisCallContext, List.of(catalog), catalogRole1); + metaStoreManager.createEntityIfNotExists(polarisCallContext, List.of(catalog), catalogRole2); + metaStoreManager.grantUsageOnRoleToGrantee( + polarisCallContext, catalog, catalogRole1, principalRole1); + metaStoreManager.grantUsageOnRoleToGrantee( + polarisCallContext, catalog, catalogRole2, principalRole2); + metaStoreManager.grantUsageOnRoleToGrantee(polarisCallContext, null, principalRole1, principal); + metaStoreManager.grantUsageOnRoleToGrantee(polarisCallContext, null, principalRole2, principal); + + PolarisBaseEntity ns1 = + metaStoreManager + .createEntityIfNotExists( + polarisCallContext, + List.of(catalog), + new NamespaceEntity.Builder(Namespace.of(NS_1)) + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setCatalogId(catalog.getId()) + .setParentId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build()) + .getEntity(); + + Namespace ns2Namespace = Namespace.of(NS_1, NS_2); + PolarisBaseEntity ns2 = + metaStoreManager + .createEntityIfNotExists( + polarisCallContext, + List.of(catalog, ns1), + new NamespaceEntity.Builder(ns2Namespace) + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setCatalogId(catalog.getId()) + .setParentId(ns1.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build()) + .getEntity(); + + TableIdentifier tbId = TableIdentifier.of(ns2Namespace, TABLE_1); + PolarisBaseEntity tb1 = + metaStoreManager + .createEntityIfNotExists( + polarisCallContext, + List.of(catalog), + new TableLikeEntity.Builder(tbId, "file://abc") + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setCatalogId(catalog.getId()) + .setParentId(ns2.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build()) + .getEntity(); + metaStoreManager.grantPrivilegeOnSecurableToRole( + polarisCallContext, + new PolarisEntityCore(catalogRole1), + List.of(PolarisEntity.toCore(catalog)), + PolarisEntity.toCore(ns1), + PolarisPrivilege.NAMESPACE_LIST); + metaStoreManager.grantPrivilegeOnSecurableToRole( + polarisCallContext, + new PolarisEntityCore(catalogRole1), + List.of(PolarisEntity.toCore(catalog), PolarisEntity.toCore(ns1)), + PolarisEntity.toCore(ns2), + PolarisPrivilege.TABLE_CREATE); + metaStoreManager.grantPrivilegeOnSecurableToRole( + polarisCallContext, + new PolarisEntityCore(catalogRole2), + List.of( + PolarisEntity.toCore(catalog), PolarisEntity.toCore(ns1), PolarisEntity.toCore(ns2)), + PolarisEntity.toCore(tb1), + PolarisPrivilege.TABLE_READ_DATA); + } + + @Test + public void testReadFromResolverPopulatedCache() { + Namespace ns2 = Namespace.of(NS_1, NS_2); + TableIdentifier tbId = TableIdentifier.of(ns2, TABLE_1); + EntityCache entityCache = new EntityCache(metaStoreManager); + PolarisResolutionManifest manifest = + new PolarisResolutionManifest( + CallContext.of(realmContext, polarisCallContext), + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache), + new AuthenticatedPolarisPrincipal( + principal, Set.of(principalRole1.getName(), principalRole2.getName())), + catalog.getName()); + manifest.addPath(new ResolverPath(List.of(NS_1, NS_2), PolarisEntityType.NAMESPACE), ns2); + manifest.addPath( + new ResolverPath(List.of(NS_1, NS_2, TABLE_1), PolarisEntityType.TABLE_LIKE), tbId); + + ResolverStatus resolverStatus = manifest.resolveAll(); + Assertions.assertThat(resolverStatus.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS); + + // now create an EntityCacheGrantManager with a dummy grantManager implementation. + // everything should be returned from the cache + EntityCacheGrantManager grantManager = new EntityCacheGrantManager(Mockito.mock(), entityCache); + PolarisResolvedPathWrapper resolvedNamespace = manifest.getResolvedPath(ns2, false); + + PolarisGrantManager.LoadGrantsResult grantsResult = + grantManager.loadGrantsOnSecurable( + polarisCallContext, + manifest.getResolvedReferenceCatalogEntity().getRawLeafEntity().getId(), + resolvedNamespace.getRawLeafEntity().getId()); + Assertions.assertThat(grantsResult) + .returns(true, PolarisGrantManager.LoadGrantsResult::isSuccess) + .extracting(PolarisGrantManager.LoadGrantsResult::getGrantRecords) + .asInstanceOf(InstanceOfAssertFactories.list(PolarisGrantRecord.class)) + .hasSize(1) + .satisfiesExactly( + grant -> { + Assertions.assertThat(grant) + .returns(catalogRole1.getId(), PolarisGrantRecord::getGranteeId) + .returns( + PolarisPrivilege.TABLE_CREATE.getCode(), + PolarisGrantRecord::getPrivilegeCode); + }); + + PolarisResolvedPathWrapper resolvedTable = manifest.getResolvedPath(tbId, false); + PolarisGrantManager.LoadGrantsResult tbGrantsResult = + grantManager.loadGrantsOnSecurable( + polarisCallContext, + manifest.getResolvedReferenceCatalogEntity().getRawLeafEntity().getId(), + resolvedTable.getRawLeafEntity().getId()); + Assertions.assertThat(tbGrantsResult) + .returns(true, PolarisGrantManager.LoadGrantsResult::isSuccess) + .extracting(PolarisGrantManager.LoadGrantsResult::getGrantRecords) + .asInstanceOf(InstanceOfAssertFactories.list(PolarisGrantRecord.class)) + .hasSize(1) + .satisfiesExactly( + grant -> { + Assertions.assertThat(grant) + .returns(catalogRole2.getId(), PolarisGrantRecord::getGranteeId) + .returns( + PolarisPrivilege.TABLE_READ_DATA.getCode(), + PolarisGrantRecord::getPrivilegeCode); + }); + } + + @Test + public void testImplicitRootContainerGrantForServiceAdmin() { + Namespace ns2 = Namespace.of(PolarisEntityConstants.getRootContainerName()); + PolarisMetaStoreManager.EntityResult namespaceEntity = + metaStoreManager.createEntityIfNotExists( + polarisCallContext, + List.of(catalog), + new NamespaceEntity.Builder(ns2) + .setId(metaStoreManager.generateNewEntityId(polarisCallContext).getId()) + .setCatalogId(catalog.getId()) + .setParentId(catalog.getId()) + .setCreateTimestamp(System.currentTimeMillis()) + .setEntityVersion(1) + .build()); + EntityCache entityCache = new EntityCache(metaStoreManager); + PolarisResolutionManifest manifest = + new PolarisResolutionManifest( + CallContext.of(realmContext, polarisCallContext), + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache), + new AuthenticatedPolarisPrincipal( + principal, Set.of(principalRole1.getName(), principalRole2.getName())), + catalog.getName()); + manifest.addPath( + new ResolverPath( + List.of(PolarisEntityConstants.getRootContainerName()), PolarisEntityType.NAMESPACE), + ns2); + + ResolverStatus resolverStatus = manifest.resolveAll(); + Assertions.assertThat(resolverStatus.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS); + + // now create an EntityCacheGrantManager with a dummy grantManager implementation. + // everything should be returned from the cache + EntityCacheGrantManager grantManager = new EntityCacheGrantManager(Mockito.mock(), entityCache); + + PolarisBaseEntity serviceAdmin = + metaStoreManager + .readEntityByName( + polarisCallContext, + null, + PolarisEntityType.PRINCIPAL_ROLE, + PolarisEntitySubType.NULL_SUBTYPE, + PolarisEntityConstants.getNameOfPrincipalServiceAdminRole()) + .getEntity(); + PolarisGrantManager.LoadGrantsResult grantsResult = + grantManager.loadGrantsOnSecurable( + polarisCallContext, 0L, PolarisEntityConstants.getRootEntityId()); + Assertions.assertThat(grantsResult) + .returns(true, PolarisGrantManager.LoadGrantsResult::isSuccess) + .extracting(PolarisGrantManager.LoadGrantsResult::getGrantRecords) + .asInstanceOf(InstanceOfAssertFactories.list(PolarisGrantRecord.class)) + .hasSize(1) + .satisfiesExactly( + grant -> { + Assertions.assertThat(grant) + .returns(serviceAdmin.getId(), PolarisGrantRecord::getGranteeId) + .returns( + PolarisPrivilege.SERVICE_MANAGE_ACCESS.getCode(), + PolarisGrantRecord::getPrivilegeCode); + }); + + PolarisResolvedPathWrapper resolvedTable = manifest.getResolvedPath(ns2, false); + PolarisGrantManager.LoadGrantsResult nsGrantsResult = + grantManager.loadGrantsOnSecurable( + polarisCallContext, + manifest.getResolvedReferenceCatalogEntity().getRawLeafEntity().getId(), + resolvedTable.getRawLeafEntity().getId()); + Assertions.assertThat(nsGrantsResult) + .returns(true, PolarisGrantManager.LoadGrantsResult::isSuccess) + .extracting(PolarisGrantManager.LoadGrantsResult::getGrantRecords) + .asInstanceOf(InstanceOfAssertFactories.list(PolarisGrantRecord.class)) + .isEmpty(); + } + + @Test + public void testRevokeGrantsClearsCache() { + Namespace ns2 = Namespace.of(NS_1, NS_2); + TableIdentifier tbId = TableIdentifier.of(ns2, TABLE_1); + EntityCache entityCache = new EntityCache(metaStoreManager); + PolarisResolutionManifest manifest = + new PolarisResolutionManifest( + CallContext.of(realmContext, polarisCallContext), + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache), + new AuthenticatedPolarisPrincipal( + principal, Set.of(principalRole1.getName(), principalRole2.getName())), + catalog.getName()); + manifest.addPath(new ResolverPath(List.of(NS_1, NS_2), PolarisEntityType.NAMESPACE), ns2); + manifest.addPath( + new ResolverPath(List.of(NS_1, NS_2, TABLE_1), PolarisEntityType.TABLE_LIKE), tbId); + + ResolverStatus resolverStatus = manifest.resolveAll(); + Assertions.assertThat(resolverStatus.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS); + + PolarisGrantManager mockGrantManager = Mockito.mock(); + EntityCacheGrantManager grantManager = + new EntityCacheGrantManager(mockGrantManager, entityCache); + PolarisResolvedPathWrapper resolvedNamespace = manifest.getResolvedPath(ns2, false); + + // update the mock to return a successful response when we revoke the privilege + Mockito.when( + grantManager.revokePrivilegeOnSecurableFromRole( + polarisCallContext, + catalogRole1, + resolvedNamespace.getRawParentPath().stream() + .map(PolarisEntity::toCore) + .collect(Collectors.toList()), + resolvedNamespace.getRawLeafEntity(), + PolarisPrivilege.TABLE_CREATE)) + .thenReturn(new PolarisGrantManager.PrivilegeResult(BaseResult.ReturnStatus.SUCCESS, null)); + PolarisGrantManager.PrivilegeResult result = + grantManager.revokePrivilegeOnSecurableFromRole( + polarisCallContext, + catalogRole1, + resolvedNamespace.getRawParentPath().stream() + .map(PolarisEntity::toCore) + .collect(Collectors.toList()), + resolvedNamespace.getRawLeafEntity(), + PolarisPrivilege.TABLE_CREATE); + Assertions.assertThat(result) + .returns( + BaseResult.ReturnStatus.SUCCESS, PolarisGrantManager.PrivilegeResult::getReturnStatus); + + // the cache is cleared + Assertions.assertThat(entityCache.getEntityById(resolvedNamespace.getRawLeafEntity().getId())) + .isNull(); + + // update the mock to return a successful, but empty response when we load the grants + Mockito.when( + mockGrantManager.loadGrantsOnSecurable( + polarisCallContext, + manifest.getResolvedReferenceCatalogEntity().getRawLeafEntity().getId(), + resolvedNamespace.getRawLeafEntity().getId())) + .thenReturn(new PolarisGrantManager.LoadGrantsResult(2, List.of(), List.of())); + PolarisGrantManager.LoadGrantsResult grantsResult = + mockGrantManager.loadGrantsOnSecurable( + polarisCallContext, + manifest.getResolvedReferenceCatalogEntity().getRawLeafEntity().getId(), + resolvedNamespace.getRawLeafEntity().getId()); + Assertions.assertThat(grantsResult) + .returns(true, PolarisGrantManager.LoadGrantsResult::isSuccess) + .extracting(PolarisGrantManager.LoadGrantsResult::getGrantRecords) + .asInstanceOf(InstanceOfAssertFactories.list(PolarisGrantRecord.class)) + .isEmpty(); + } + + @Test + public void testUpdateGrantsClearsCache() { + Namespace ns2 = Namespace.of(NS_1, NS_2); + TableIdentifier tbId = TableIdentifier.of(ns2, TABLE_1); + EntityCache entityCache = new EntityCache(metaStoreManager); + PolarisResolutionManifest manifest = + new PolarisResolutionManifest( + CallContext.of(realmContext, polarisCallContext), + new PolarisEntityManager(metaStoreManager, new StorageCredentialCache(), entityCache), + new AuthenticatedPolarisPrincipal( + principal, Set.of(principalRole1.getName(), principalRole2.getName())), + catalog.getName()); + manifest.addPath(new ResolverPath(List.of(NS_1, NS_2), PolarisEntityType.NAMESPACE), ns2); + manifest.addPath( + new ResolverPath(List.of(NS_1, NS_2, TABLE_1), PolarisEntityType.TABLE_LIKE), tbId); + + ResolverStatus resolverStatus = manifest.resolveAll(); + Assertions.assertThat(resolverStatus.getStatus()).isEqualTo(ResolverStatus.StatusEnum.SUCCESS); + + EntityCacheGrantManager grantManager = + new EntityCacheGrantManager(metaStoreManager, entityCache); + PolarisResolvedPathWrapper resolvedTable = manifest.getResolvedPath(tbId, false); + + PolarisGrantManager.PrivilegeResult result = + grantManager.grantPrivilegeOnSecurableToRole( + polarisCallContext, + catalogRole2, + resolvedTable.getRawParentPath().stream() + .map(PolarisEntity::toCore) + .collect(Collectors.toList()), + resolvedTable.getRawLeafEntity(), + PolarisPrivilege.TABLE_DROP); + Assertions.assertThat(result) + .returns( + BaseResult.ReturnStatus.SUCCESS, PolarisGrantManager.PrivilegeResult::getReturnStatus); + + // the cache is cleared + Assertions.assertThat(entityCache.getEntityById(resolvedTable.getRawLeafEntity().getId())) + .isNull(); + + PolarisGrantManager.LoadGrantsResult grantsResult = + grantManager.loadGrantsOnSecurable( + polarisCallContext, + manifest.getResolvedReferenceCatalogEntity().getRawLeafEntity().getId(), + resolvedTable.getRawLeafEntity().getId()); + Assertions.assertThat(grantsResult) + .returns(true, PolarisGrantManager.LoadGrantsResult::isSuccess) + .extracting(PolarisGrantManager.LoadGrantsResult::getGrantRecords) + .asInstanceOf(InstanceOfAssertFactories.list(PolarisGrantRecord.class)) + .hasSize(2) + .satisfiesExactlyInAnyOrder( + (PolarisGrantRecord gr) -> + Assertions.assertThat(gr) + .returns(catalogRole2.getId(), PolarisGrantRecord::getGranteeId) + .returns( + PolarisPrivilege.TABLE_READ_DATA.getCode(), + PolarisGrantRecord::getPrivilegeCode), + (PolarisGrantRecord gr) -> + Assertions.assertThat(gr) + .returns(catalogRole2.getId(), PolarisGrantRecord::getGranteeId) + .returns( + PolarisPrivilege.TABLE_DROP.getCode(), + PolarisGrantRecord::getPrivilegeCode)); + } +} diff --git a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java index 7ad5cd88f6..ae4889d00f 100644 --- a/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java +++ b/service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java @@ -193,7 +193,7 @@ private void authorizeBasicTopLevelEntityOperationOrThrow( // TODO: If we do add more "self" privilege operations for PRINCIPAL targets this should // be extracted into an EnumSet and/or pushed down into PolarisAuthorizer. - if (topLevelEntityWrapper.getResolvedLeafEntity().getEntity().getId() + if (topLevelEntityWrapper.getResolvedLeafEntity().getId() == authenticatedPrincipal.getPrincipalEntity().getId() && (op.equals(PolarisAuthorizableOperation.ROTATE_CREDENTIALS) || op.equals(PolarisAuthorizableOperation.RESET_CREDENTIALS))) { diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java b/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java index 56fc2468a6..f19dab9e79 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java @@ -528,10 +528,13 @@ private String resolveNamespaceLocation(Namespace namespace, Map if (properties.containsKey(PolarisEntityConstants.ENTITY_BASE_LOCATION)) { return properties.get(PolarisEntityConstants.ENTITY_BASE_LOCATION); } else { - List parentPath = - namespace.length() > 1 - ? getResolvedParentNamespace(namespace).getRawFullPath() - : List.of(resolvedEntityView.getResolvedReferenceCatalogEntity().getRawLeafEntity()); + List parentPath; + if (namespace.length() > 1) { + parentPath = getResolvedParentNamespace(namespace).getRawFullPath(); + } else { + parentPath = + List.of(resolvedEntityView.getResolvedReferenceCatalogEntity().getRawLeafEntity()); + } String parentLocation = resolveLocationForPath(parentPath); @@ -1663,7 +1666,7 @@ private void renameTableLike( toEntity = new TableLikeEntity.Builder(TableLikeEntity.of(leafEntity)) .setTableIdentifier(to) - .setParentId(resolvedNewParentEntities.getResolvedLeafEntity().getEntity().getId()) + .setParentId(resolvedNewParentEntities.getResolvedLeafEntity().getId()) .build(); } else { // only the name of the entity is changed diff --git a/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java b/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java index 3d12f75a79..964cd79516 100644 --- a/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java +++ b/service/common/src/main/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapper.java @@ -565,11 +565,7 @@ public LoadTableResponse createTableDirect(Namespace namespace, CreateTableReque op, TableIdentifier.of(namespace, request.name())); CatalogEntity catalog = - CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + CatalogEntity.of(resolutionManifest.getResolvedReferenceCatalogEntity().getRawLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot create table on external catalogs."); } @@ -584,11 +580,7 @@ public LoadTableResponse createTableDirectWithWriteDelegation( op, TableIdentifier.of(namespace, request.name())); CatalogEntity catalog = - CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + CatalogEntity.of(resolutionManifest.getResolvedReferenceCatalogEntity().getRawLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot create table on external catalogs."); } @@ -694,10 +686,7 @@ public LoadTableResponse createTableStaged(Namespace namespace, CreateTableReque CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot create table on external catalogs."); } @@ -717,10 +706,7 @@ public LoadTableResponse createTableStagedWithWriteDelegation( CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot create table on external catalogs."); } @@ -774,10 +760,7 @@ public boolean sendNotification(TableIdentifier identifier, NotificationRequest CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (catalog .getCatalogType() .equals(org.apache.polaris.core.admin.model.Catalog.TypeEnum.INTERNAL)) { @@ -904,10 +887,7 @@ public LoadTableResponse updateTable( CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot update table on external catalogs."); } @@ -923,10 +903,7 @@ public LoadTableResponse updateTableForStagedCreate( CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot update table on external catalogs."); } @@ -948,10 +925,7 @@ public void dropTableWithPurge(TableIdentifier tableIdentifier) { CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot drop table on external catalogs."); } @@ -973,10 +947,7 @@ public void renameTable(RenameTableRequest request) { CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot rename table on external catalogs."); } @@ -997,10 +968,7 @@ public void commitTransaction(CommitTransactionRequest commitTransactionRequest) .toList()); CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot update table on external catalogs."); } @@ -1104,10 +1072,7 @@ public LoadViewResponse createView(Namespace namespace, CreateViewRequest reques CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot create view on external catalogs."); } @@ -1127,10 +1092,7 @@ public LoadViewResponse replaceView(TableIdentifier viewIdentifier, UpdateTableR CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot replace view on external catalogs."); } @@ -1160,10 +1122,7 @@ public void renameView(RenameTableRequest request) { CatalogEntity catalog = CatalogEntity.of( - resolutionManifest - .getResolvedReferenceCatalogEntity() - .getResolvedLeafEntity() - .getEntity()); + resolutionManifest.getResolvedReferenceCatalogEntity().getResolvedLeafEntity()); if (isExternal(catalog)) { throw new BadRequestException("Cannot rename view on external catalogs."); } diff --git a/service/common/src/main/java/org/apache/polaris/service/config/RealmEntityManagerFactory.java b/service/common/src/main/java/org/apache/polaris/service/config/RealmEntityManagerFactory.java index 620e679006..b8042e372f 100644 --- a/service/common/src/main/java/org/apache/polaris/service/config/RealmEntityManagerFactory.java +++ b/service/common/src/main/java/org/apache/polaris/service/config/RealmEntityManagerFactory.java @@ -20,6 +20,7 @@ import jakarta.inject.Inject; import jakarta.inject.Provider; +import jakarta.validation.constraints.NotNull; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.polaris.core.context.RealmContext; @@ -46,7 +47,8 @@ protected RealmEntityManagerFactory() { @Inject public RealmEntityManagerFactory( - MetaStoreManagerFactory metaStoreManagerFactory, Provider entityCache) { + @NotNull MetaStoreManagerFactory metaStoreManagerFactory, + @NotNull Provider entityCache) { this.metaStoreManagerFactory = metaStoreManagerFactory; this.entityCache = entityCache; } @@ -55,7 +57,6 @@ public PolarisEntityManager getOrCreateEntityManager(RealmContext context) { String realm = context.getRealmIdentifier(); LOGGER.debug("Looking up PolarisEntityManager for realm {}", realm); - return cachedEntityManagers.computeIfAbsent( realm, r -> {