You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by ad...@apache.org on 2026/10/07 13:08:25 UTC
(polaris) branch main updated: Move credential rotation check from authorizers to a JAX-RS filter (#5706)
This is an automated email from the ASF dual-hosted git repository.
adutra pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new a735681d82 Move credential rotation check from authorizers to a JAX-RS filter (#5706)
a735681d82 is described below
commit a735681d82cbfb8b4e71cc446c86da0b4d650302
Author: Alexandre Dutra <ad...@apache.org>
AuthorDate: Wed Oct 7 15:08:19 2026 +0200
Move credential rotation check from authorizers to a JAX-RS filter (#5706)
The check that blocks principals who must rotate their credentials was
duplicated in each authorizer implementation. Enforce it once in a
request filter so it applies regardless of the configured authorizer.
---
CHANGELOG.md | 10 +-
.../extension/auth/opa/OpaPolarisAuthorizer.java | 16 +--
.../auth/opa/OpaPolarisAuthorizerFactory.java | 3 +-
.../auth/opa/OpaPolarisAuthorizerFactoryTest.java | 27 ++--
.../auth/opa/OpaPolarisAuthorizerTest.java | 147 +++------------------
.../auth/ranger/RangerPolarisAuthorizer.java | 26 ++--
.../ranger/RangerPolarisAuthorizerFactory.java | 9 +-
.../auth/ranger/RangerPolarisAuthorizerTest.java | 4 +-
.../SemanticModelCatalogHandlerAuthzTest.java | 2 +-
.../core/auth/AuthorizationPreConditions.java | 70 ----------
.../polaris/core/auth/PolarisAuthorizerImpl.java | 9 --
.../core/auth/AuthorizationPreConditionsTest.java | 104 ---------------
.../core/auth/PolarisAuthorizerImplTest.java | 44 ++----
.../service/auth/CredentialRotationFilter.java | 91 +++++++++++++
.../auth/DefaultPolarisAuthorizerFactory.java | 2 +-
.../polaris/service/config/FilterPriorities.java | 15 ++-
.../service/admin/ManagementServiceTest.java | 2 +-
.../service/admin/PolarisAuthzTestBase.java | 2 +-
.../service/auth/CredentialRotationFilterTest.java | 137 +++++++++++++++++++
.../AbstractPolarisGenericTableCatalogTest.java | 2 +-
.../AbstractIcebergCatalogHandlerAuthzTest.java | 140 --------------------
.../AbstractLocalIcebergCatalogViewTest.java | 2 +-
.../catalog/policy/AbstractPolicyCatalogTest.java | 2 +-
23 files changed, 308 insertions(+), 558 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3cad49e65..87bbe2c9d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -130,10 +130,12 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
`deserializeKey`. Previously it counted two units per escaped `0x01`/`0x02` byte against the
same 500 limit, so a key that had been written successfully (for example 251 bytes of `0x01`)
could make index deserialization throw and leave the index container unreadable.
-- The OPA authorizer now enforces the credential-rotation pre-condition. With
- `ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING` enabled, a principal whose credentials
- were reset but never rotated was refused by the internal and Ranger authorizers but not by OPA.
- The rotation state is not part of the OPA input document, so a policy could not compensate.
+- The credential-rotation pre-condition is now enforced by a JAX-RS filter instead of by each
+ authorizer. With `ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING` enabled, a principal
+ whose credentials were reset but never rotated can only call the rotate-credentials endpoint,
+ whichever authorizer is configured. Previously the internal and Ranger authorizers enforced it
+ but OPA did not, and the rotation state is not part of the OPA input document, so a policy could
+ not compensate.
- Re-creating an existing namespace now returns `409 Conflict` instead of `403 Forbidden` when
`OPTIMIZED_SIBLING_CHECK` is on. Namespace creation checks for an existing namespace before
validating locations, as table and view creation already do, so the existing namespace's own
diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
index da91c1a1d2..a7d0646757 100644
--- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
+++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizer.java
@@ -37,10 +37,8 @@ import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.HttpClientResponseHandler;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
-import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.polaris.core.auth.AuthorizationDecision;
import org.apache.polaris.core.auth.AuthorizationIntent;
-import org.apache.polaris.core.auth.AuthorizationPreConditions;
import org.apache.polaris.core.auth.AuthorizationRequest;
import org.apache.polaris.core.auth.AuthorizationState;
import org.apache.polaris.core.auth.PathSegment;
@@ -55,7 +53,6 @@ import org.apache.polaris.core.auth.RoleAssignmentAuthorizationIntent;
import org.apache.polaris.core.auth.RootPrivilegeGrantAuthorizationIntent;
import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent;
import org.apache.polaris.core.auth.TargetlessAuthorizationIntent;
-import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.extension.auth.opa.model.ImmutableActor;
import org.apache.polaris.extension.auth.opa.model.ImmutableContext;
import org.apache.polaris.extension.auth.opa.model.ImmutableOpaAuthorizationInput;
@@ -89,7 +86,6 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer {
private final ObjectMapper objectMapper;
private final String requestId;
private final String realm;
- private final RealmConfig realmConfig;
/**
* Public constructor that accepts a complete policy URI and the current realm identifier.
@@ -105,8 +101,6 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer {
* the originating HTTP request. Resolved once by the caller since this authorizer is
* constructed fresh per request.
* @param realm The realm identifier (from RealmContext) for isolation in OPA policies.
- * @param realmConfig The realm configuration, used to evaluate authorization pre-conditions that
- * Polaris enforces before consulting OPA.
*/
public OpaPolarisAuthorizer(
@NonNull URI policyUri,
@@ -114,8 +108,7 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer {
@NonNull ObjectMapper objectMapper,
@Nullable BearerTokenProvider tokenProvider,
@Nullable String requestId,
- @NonNull String realm,
- @NonNull RealmConfig realmConfig) {
+ @NonNull String realm) {
this.policyUri = policyUri;
this.tokenProvider = tokenProvider;
@@ -123,7 +116,6 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer {
this.objectMapper = objectMapper;
this.requestId = requestId;
this.realm = realm;
- this.realmConfig = realmConfig;
}
/**
@@ -144,12 +136,6 @@ class OpaPolarisAuthorizer implements PolarisAuthorizer {
@NonNull AuthorizationState authzState, @NonNull AuthorizationRequest request) {
for (AuthorizationIntent intent : request.intents()) {
PolarisAuthorizableOperation operation = intent.operation();
- try {
- AuthorizationPreConditions.checkCredentialRotationRequired(
- request.principal(), operation, realmConfig);
- } catch (ForbiddenException e) {
- return AuthorizationDecision.deny(e.getMessage());
- }
List<ResourceEntity> targets;
List<ResourceEntity> secondaries;
switch (intent) {
diff --git a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java
index e530e864ac..ee3d62c4be 100644
--- a/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java
+++ b/extensions/auth/opa/src/main/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactory.java
@@ -111,8 +111,7 @@ class OpaPolarisAuthorizerFactory implements PolarisAuthorizerFactory {
objectMapper,
bearerTokenProvider,
requestIdSupplier.getRequestId(),
- realmContext.getRealmIdentifier(),
- realmConfig);
+ realmContext.getRealmIdentifier());
}
@PreDestroy
diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java
index 405e6ab79b..c70a6a683d 100644
--- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java
+++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerFactoryTest.java
@@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -50,7 +49,6 @@ import org.apache.polaris.core.auth.TargetlessAuthorizationIntent;
import org.apache.polaris.core.collection.AttributeMap;
import org.apache.polaris.core.collection.AttributeMap.AttributeKey;
import org.apache.polaris.core.collection.ImmutableAttributeMap;
-import org.apache.polaris.core.config.FeatureConfiguration;
import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
@@ -94,7 +92,7 @@ public class OpaPolarisAuthorizerFactoryTest {
opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext);
// Create authorizer
- RealmConfig realmConfig = realmConfig(false);
+ RealmConfig realmConfig = mock(RealmConfig.class);
OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig);
assertThat(authorizer).isNotNull();
@@ -140,7 +138,7 @@ public class OpaPolarisAuthorizerFactoryTest {
opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext);
// Create authorizer
- RealmConfig realmConfig = realmConfig(false);
+ RealmConfig realmConfig = mock(RealmConfig.class);
OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig);
assertThat(authorizer).isNotNull();
@@ -187,7 +185,7 @@ public class OpaPolarisAuthorizerFactoryTest {
opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext);
// Create authorizer
- RealmConfig realmConfig = realmConfig(false);
+ RealmConfig realmConfig = mock(RealmConfig.class);
OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig);
assertThat(authorizer).isNotNull();
@@ -225,7 +223,8 @@ public class OpaPolarisAuthorizerFactoryTest {
opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext);
factory.initialize();
- OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig(false));
+ OpaPolarisAuthorizer authorizer =
+ (OpaPolarisAuthorizer) factory.create(mock(RealmConfig.class));
PolarisPrincipal principal =
PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("admin"));
@@ -284,7 +283,8 @@ public class OpaPolarisAuthorizerFactoryTest {
opaConfig, Clock.systemUTC(), asyncExec, () -> null, realmContext);
factory.initialize();
- OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig(false));
+ OpaPolarisAuthorizer authorizer =
+ (OpaPolarisAuthorizer) factory.create(mock(RealmConfig.class));
PolarisPrincipal principal =
PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("admin"));
@@ -346,7 +346,7 @@ public class OpaPolarisAuthorizerFactoryTest {
realmContext);
factory.initialize();
- RealmConfig realmConfig = realmConfig(false);
+ RealmConfig realmConfig = mock(RealmConfig.class);
OpaPolarisAuthorizer authorizer = (OpaPolarisAuthorizer) factory.create(realmConfig);
PolarisPrincipal principal =
@@ -417,7 +417,7 @@ public class OpaPolarisAuthorizerFactoryTest {
factory.initialize();
- RealmConfig realmConfig = realmConfig(false);
+ RealmConfig realmConfig = mock(RealmConfig.class);
PolarisPrincipal principal =
PolarisPrincipal.of(
"eve",
@@ -517,13 +517,4 @@ public class OpaPolarisAuthorizerFactoryTest {
server.start();
return server;
}
-
- /** A {@link RealmConfig} answering the credential-rotation flag, as {@code polaris-core} does. */
- private static RealmConfig realmConfig(boolean enforceCredentialRotation) {
- RealmConfig realmConfig = mock(RealmConfig.class);
- when(realmConfig.getConfig(
- FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING))
- .thenReturn(enforceCredentialRotation);
- return realmConfig;
- }
}
diff --git a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java
index e9ed506ee8..c5a14fe437 100644
--- a/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java
+++ b/extensions/auth/opa/src/test/java/org/apache/polaris/extension/auth/opa/OpaPolarisAuthorizerTest.java
@@ -22,7 +22,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -57,7 +56,6 @@ import org.apache.polaris.core.auth.AuthorizationState;
import org.apache.polaris.core.auth.PathSegment;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.apache.polaris.core.auth.PolarisPrincipalAttributes;
import org.apache.polaris.core.auth.PolarisSecurable;
import org.apache.polaris.core.auth.RenameAuthorizationIntent;
import org.apache.polaris.core.auth.SingleTargetAuthorizationIntent;
@@ -65,12 +63,9 @@ import org.apache.polaris.core.auth.TargetlessAuthorizationIntent;
import org.apache.polaris.core.collection.AttributeMap;
import org.apache.polaris.core.collection.AttributeMap.AttributeKey;
import org.apache.polaris.core.collection.ImmutableAttributeMap;
-import org.apache.polaris.core.config.FeatureConfiguration;
-import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntityConstants;
import org.apache.polaris.core.entity.PolarisEntityType;
-import org.apache.polaris.core.entity.PrincipalEntity;
import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
import org.apache.polaris.core.persistence.ResolvedPolarisEntity;
import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
@@ -101,8 +96,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisPrincipal principal =
PolarisPrincipal.of(
@@ -164,8 +158,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "prod-realm",
- realmConfig(false));
+ "prod-realm");
// Set up a realistic principal
PolarisPrincipal principal =
@@ -310,8 +303,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "analytics-realm",
- realmConfig(false));
+ "analytics-realm");
// Set up a realistic principal
PolarisPrincipal principal =
@@ -488,8 +480,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisPrincipal principal =
PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("admin"));
@@ -545,8 +536,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
tokenProvider,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
assertThat(authorizer).isNotNull();
}
@@ -567,8 +557,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
tokenProvider,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -621,8 +610,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
tokenProvider,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -670,8 +658,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(resolutionManifest);
PolarisPrincipal principal = PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("role-1"));
@@ -700,8 +687,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -751,8 +737,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -795,8 +780,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "catalog-realm",
- realmConfig(false)) {
+ "catalog-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -846,8 +830,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisEntity rootEntity =
new PolarisEntity.Builder()
@@ -921,8 +904,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisEntity rootEntity =
new PolarisEntity.Builder()
@@ -992,8 +974,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -1060,8 +1041,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -1149,8 +1129,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false)) {
+ "test-realm") {
@Override
<T> T httpClientExecute(
ClassicHttpRequest request, HttpClientResponseHandler<? extends T> responseHandler)
@@ -1191,8 +1170,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "explicit-realm",
- realmConfig(false));
+ "explicit-realm");
PolarisPrincipal principal =
PolarisPrincipal.of("eve", AttributeMap.EMPTY, Set.of("auditor"));
@@ -1235,8 +1213,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "tenant-xyz",
- realmConfig(false));
+ "tenant-xyz");
PolarisResolutionManifest resolutionManifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(resolutionManifest);
@@ -1271,8 +1248,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
"test-id",
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisPrincipal principal =
PolarisPrincipal.of(
@@ -1322,8 +1298,7 @@ public class OpaPolarisAuthorizerTest {
JsonMapper.builder().build(),
null,
null,
- "test-realm",
- realmConfig(false));
+ "test-realm");
PolarisPrincipal principal =
PolarisPrincipal.of(
@@ -1501,86 +1476,4 @@ public class OpaPolarisAuthorizerTest {
.isFalse();
}
}
-
- @Test
- void authorizeRejectsPrincipalThatMustRotateCredentials() {
- // An unreachable policy URI: contacting OPA at all would surface as a connection failure, so a
- // clean denial proves the pre-condition is evaluated before OPA is consulted.
- OpaPolarisAuthorizer authorizer =
- new OpaPolarisAuthorizer(
- URI.create("http://127.0.0.1:1/v1/data/polaris/allow"),
- HttpClients.createDefault(),
- JsonMapper.builder().build(),
- null,
- null,
- "test-realm",
- realmConfig(true));
-
- AuthorizationDecision decision =
- authorizer.authorize(
- new AuthorizationState(mock(PolarisResolutionManifest.class)),
- new AuthorizationRequest(
- rotationRequiredPrincipal(),
- authorizationIntents(PolarisAuthorizableOperation.LOAD_TABLE, null, null)));
-
- assertThat(decision.isAllowed()).isFalse();
- assertThat(decision.getMessage())
- .hasValueSatisfying(
- message -> assertThat(message).contains("must rotate credentials first"));
- }
-
- @Test
- void authorizeAllowsCredentialRotationForPrincipalThatMustRotate() throws Exception {
- HttpServer server = createServerWithAllowResponse();
- try {
- OpaPolarisAuthorizer authorizer =
- new OpaPolarisAuthorizer(
- URI.create(
- "http://localhost:" + server.getAddress().getPort() + "/v1/data/polaris/allow"),
- HttpClients.createDefault(),
- JsonMapper.builder().build(),
- null,
- null,
- "test-realm",
- realmConfig(true));
-
- assertThatNoException()
- .isThrownBy(
- () ->
- authorizer
- .authorize(
- new AuthorizationState(mock(PolarisResolutionManifest.class)),
- new AuthorizationRequest(
- rotationRequiredPrincipal(),
- authorizationIntents(
- PolarisAuthorizableOperation.ROTATE_CREDENTIALS, null, null)))
- .throwIfDenied());
- } finally {
- server.stop(0);
- }
- }
-
- /** A principal flagged as needing to rotate its credentials before doing anything else. */
- private static PolarisPrincipal rotationRequiredPrincipal() {
- return PolarisPrincipal.of(
- "alice",
- ImmutableAttributeMap.builder()
- .put(
- PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY,
- new PrincipalEntity.Builder()
- .setName("alice")
- .setCredentialRotationRequiredState()
- .build())
- .build(),
- Set.of("role"));
- }
-
- /** A {@link RealmConfig} answering the credential-rotation flag, as {@code polaris-core} does. */
- private static RealmConfig realmConfig(boolean enforceCredentialRotation) {
- RealmConfig realmConfig = mock(RealmConfig.class);
- when(realmConfig.getConfig(
- FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING))
- .thenReturn(enforceCredentialRotation);
- return realmConfig;
- }
}
diff --git a/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java b/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java
index 267a27fba1..8d2f3ed43b 100644
--- a/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java
+++ b/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizer.java
@@ -26,13 +26,11 @@ import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.polaris.core.auth.AuthorizationDecision;
import org.apache.polaris.core.auth.AuthorizationIntent;
import org.apache.polaris.core.auth.AuthorizationIntentResolver;
-import org.apache.polaris.core.auth.AuthorizationPreConditions;
import org.apache.polaris.core.auth.AuthorizationRequest;
import org.apache.polaris.core.auth.AuthorizationState;
import org.apache.polaris.core.auth.PolarisAuthorizableOperation;
import org.apache.polaris.core.auth.PolarisAuthorizer;
import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.core.context.RealmContext;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.persistence.PolarisResolvedPathWrapper;
@@ -62,20 +60,15 @@ public class RangerPolarisAuthorizer implements PolarisAuthorizer {
private final RangerEmbeddedAuthorizer authorizer;
private final String serviceName;
- private RealmContext realmContext;
- private String realmConextIdentifier;
- private final RealmConfig realmConfig;
+ private String realmContextIdentifier;
- public RangerPolarisAuthorizer(
- RangerEmbeddedAuthorizer authorizer, String serviceName, RealmConfig realmConfig) {
+ public RangerPolarisAuthorizer(RangerEmbeddedAuthorizer authorizer, String serviceName) {
this.authorizer = authorizer;
this.serviceName = serviceName;
- this.realmConfig = realmConfig;
}
- public void setRealmContext(RealmContext aRealmContext) {
- this.realmContext = aRealmContext;
- this.realmConextIdentifier = aRealmContext.getRealmIdentifier();
+ public void setRealmContext(RealmContext realmContext) {
+ this.realmContextIdentifier = realmContext.getRealmIdentifier();
}
/**
@@ -161,9 +154,6 @@ public class RangerPolarisAuthorizer implements PolarisAuthorizer {
}
try {
- AuthorizationPreConditions.checkCredentialRotationRequired(
- polarisPrincipal, authzOp, realmConfig);
-
if (!isAccessAuthorized(polarisPrincipal, authzOp, targets, secondaries)) {
throw new ForbiddenException(
RANGER_AUTH_FAILED_ERROR, polarisPrincipal.getName(), authzOp.name());
@@ -197,12 +187,12 @@ public class RangerPolarisAuthorizer implements PolarisAuthorizer {
for (PolarisResolvedPathWrapper target : targets) {
accessInfos.add(
RangerUtils.toAccessInfo(
- target, authzOp, semantics.targetPrivileges(), realmConextIdentifier));
+ target, authzOp, semantics.targetPrivileges(), realmContextIdentifier));
}
} else if (isTargetSpecified) {
LOG.warn(
"No privileges specified for target authorization. Ignoring target {}, op: {}, user: {}",
- RangerUtils.toResourcePath(targets, realmConextIdentifier),
+ RangerUtils.toResourcePath(targets, realmContextIdentifier),
authzOp.name(),
principal.getName());
}
@@ -217,12 +207,12 @@ public class RangerPolarisAuthorizer implements PolarisAuthorizer {
for (PolarisResolvedPathWrapper secondary : secondaries) {
accessInfos.add(
RangerUtils.toAccessInfo(
- secondary, authzOp, semantics.secondaryPrivileges(), realmConextIdentifier));
+ secondary, authzOp, semantics.secondaryPrivileges(), realmContextIdentifier));
}
} else if (isSecondarySpecified) {
LOG.warn(
"No privileges specified for secondary authorization. Ignoring secondaries {}, op: {}, user: {}",
- RangerUtils.toResourcePath(secondaries, realmConextIdentifier),
+ RangerUtils.toResourcePath(secondaries, realmContextIdentifier),
authzOp.name(),
principal.getName());
}
diff --git a/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerFactory.java b/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerFactory.java
index d1787cd994..2bdcbd4cd3 100644
--- a/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerFactory.java
+++ b/extensions/auth/ranger/src/main/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerFactory.java
@@ -39,14 +39,13 @@ public class RangerPolarisAuthorizerFactory implements PolarisAuthorizerFactory
private static final String ERR_AUTHORIZER_FACTORY_NOT_INITIALIZED =
"Ranger authorizer factory was not initialized successfully";
- private final RangerPolarisAuthorizerConfig config;
- private RangerEmbeddedAuthorizer authorizer;
- private String serviceName;
+ private final RangerEmbeddedAuthorizer authorizer;
+ private final String serviceName;
+
@Inject private RealmContext realmContext;
@Inject
RangerPolarisAuthorizerFactory(RangerPolarisAuthorizerConfig config) {
- this.config = config;
config.validate();
LOG.info("Initializing RangerAuthorizer");
try {
@@ -71,7 +70,7 @@ public class RangerPolarisAuthorizerFactory implements PolarisAuthorizerFactory
}
RangerPolarisAuthorizer polarisAuthorizer =
- new RangerPolarisAuthorizer(authorizer, serviceName, realmConfig);
+ new RangerPolarisAuthorizer(authorizer, serviceName);
if (realmContext != null) {
polarisAuthorizer.setRealmContext(realmContext);
diff --git a/extensions/auth/ranger/src/test/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerTest.java b/extensions/auth/ranger/src/test/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerTest.java
index 3166b2f7f5..9118af8363 100644
--- a/extensions/auth/ranger/src/test/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerTest.java
+++ b/extensions/auth/ranger/src/test/java/org/apache/polaris/extension/auth/ranger/RangerPolarisAuthorizerTest.java
@@ -211,7 +211,7 @@ public class RangerPolarisAuthorizerTest {
void authorizeUsesIntentResolvedTarget() throws Exception {
RangerEmbeddedAuthorizer embeddedAuthorizer = mock(RangerEmbeddedAuthorizer.class);
RangerPolarisAuthorizer authorizer =
- new RangerPolarisAuthorizer(embeddedAuthorizer, "dev_polaris", createRealmConfig());
+ new RangerPolarisAuthorizer(embeddedAuthorizer, "dev_polaris");
authorizer.setRealmContext(createRealmContext());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
PolarisResolvedPathWrapper tablePath =
@@ -264,7 +264,7 @@ public class RangerPolarisAuthorizerTest {
void authorizeReturnsDenyDecisionWhenRangerDenies() throws Exception {
RangerEmbeddedAuthorizer embeddedAuthorizer = mock(RangerEmbeddedAuthorizer.class);
RangerPolarisAuthorizer authorizer =
- new RangerPolarisAuthorizer(embeddedAuthorizer, "dev_polaris", createRealmConfig());
+ new RangerPolarisAuthorizer(embeddedAuthorizer, "dev_polaris");
authorizer.setRealmContext(createRealmContext());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
PolarisResolvedPathWrapper catalogPath =
diff --git a/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/SemanticModelCatalogHandlerAuthzTest.java b/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/SemanticModelCatalogHandlerAuthzTest.java
index d7b6b0178a..d87989736c 100644
--- a/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/SemanticModelCatalogHandlerAuthzTest.java
+++ b/extensions/semantic-models/src/test/java/org/apache/polaris/service/catalog/semanticmodel/SemanticModelCatalogHandlerAuthzTest.java
@@ -420,7 +420,7 @@ class SemanticModelCatalogHandlerAuthzTest extends AbstractSemanticModelCatalogH
.callContext(services.newCallContext())
.resolutionManifestFactory(services.resolutionManifestFactory())
.metaStoreManager(services.metaStoreManager())
- .authorizer(new PolarisAuthorizerImpl(services.realmConfig()))
+ .authorizer(new PolarisAuthorizerImpl())
.build();
}
}
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationPreConditions.java b/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationPreConditions.java
deleted file mode 100644
index 6d1cddd566..0000000000
--- a/polaris-core/src/main/java/org/apache/polaris/core/auth/AuthorizationPreConditions.java
+++ /dev/null
@@ -1,70 +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.auth;
-
-import org.apache.iceberg.exceptions.ForbiddenException;
-import org.apache.polaris.core.config.FeatureConfiguration;
-import org.apache.polaris.core.config.RealmConfig;
-import org.apache.polaris.core.entity.PolarisEntityConstants;
-import org.apache.polaris.core.entity.PrincipalEntity;
-
-/**
- * Common pre-condition checks shared across authorizer implementations for credential-related
- * operations.
- */
-public final class AuthorizationPreConditions {
-
- private AuthorizationPreConditions() {}
-
- /**
- * Checks whether the principal is required to rotate credentials before performing the requested
- * operation. If the principal has the {@code PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_STATE}
- * property set, only {@link PolarisAuthorizableOperation#ROTATE_CREDENTIALS} is allowed.
- *
- * @param polarisPrincipal the principal attempting the operation
- * @param authzOp the operation being attempted
- * @param realmConfig the realm config, used to read the enforcement feature flag
- * @throws ForbiddenException if the principal must rotate credentials first
- */
- public static void checkCredentialRotationRequired(
- PolarisPrincipal polarisPrincipal,
- PolarisAuthorizableOperation authzOp,
- RealmConfig realmConfig) {
- if (realmConfig.getConfig(
- FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING)
- && authzOp != PolarisAuthorizableOperation.ROTATE_CREDENTIALS
- && mustRotateCredentials(polarisPrincipal)) {
- throw new ForbiddenException(
- "Principal '%s' is not authorized for op %s because it must rotate credentials first",
- polarisPrincipal.getName(), authzOp);
- }
- }
-
- private static boolean mustRotateCredentials(PolarisPrincipal principal) {
- return principal
- .getAttributes()
- .getOptional(PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY)
- .map(PrincipalEntity::getInternalPropertiesAsMap)
- .map(
- map ->
- map.containsKey(
- PolarisEntityConstants.PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_STATE))
- .orElse(false);
- }
-}
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 24c9a34ad2..622b9b8b38 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
@@ -142,7 +142,6 @@ import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.polaris.core.StructuredLogKeys;
import org.apache.polaris.core.auth.AuthorizationIntentResolver.ResolvedIntent;
import org.apache.polaris.core.auth.RbacOperationSemantics.ResolvedPathRooting;
-import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.core.entity.PolarisBaseEntity;
import org.apache.polaris.core.entity.PolarisEntityCore;
import org.apache.polaris.core.entity.PolarisGrantRecord;
@@ -795,12 +794,6 @@ public class PolarisAuthorizerImpl implements PolarisAuthorizer {
: EnumSet.of(privilege);
}
- private final RealmConfig realmConfig;
-
- public PolarisAuthorizerImpl(RealmConfig realmConfig) {
- this.realmConfig = realmConfig;
- }
-
@Override
public void resolveAuthorizationInputs(
@NonNull AuthorizationState authzState, @NonNull AuthorizationRequest request) {
@@ -872,8 +865,6 @@ public class PolarisAuthorizerImpl implements PolarisAuthorizer {
@NonNull Set<PolarisBaseEntity> activatedEntities,
@NonNull PolarisAuthorizableOperation authzOp,
@NonNull ResolvedIntent resolvedIntent) {
- AuthorizationPreConditions.checkCredentialRotationRequired(
- polarisPrincipal, authzOp, realmConfig);
boolean isRoot = getRootPrincipalName().equals(polarisPrincipal.getName());
if (authzOp == PolarisAuthorizableOperation.RESET_CREDENTIALS) {
if (!isRoot) {
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationPreConditionsTest.java b/polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationPreConditionsTest.java
deleted file mode 100644
index 0ffda57fd4..0000000000
--- a/polaris-core/src/test/java/org/apache/polaris/core/auth/AuthorizationPreConditionsTest.java
+++ /dev/null
@@ -1,104 +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.auth;
-
-import static org.assertj.core.api.Assertions.assertThatCode;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.Set;
-import org.apache.iceberg.exceptions.ForbiddenException;
-import org.apache.polaris.core.collection.AttributeMap;
-import org.apache.polaris.core.collection.ImmutableAttributeMap;
-import org.apache.polaris.core.config.FeatureConfiguration;
-import org.apache.polaris.core.config.RealmConfig;
-import org.apache.polaris.core.entity.PrincipalEntity;
-import org.junit.jupiter.api.Test;
-
-public class AuthorizationPreConditionsTest {
-
- private static final PolarisPrincipal PRINCIPAL =
- PolarisPrincipal.of(
- "alice",
- ImmutableAttributeMap.builder()
- .put(
- PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY,
- new PrincipalEntity.Builder()
- .setName("alice")
- .setCredentialRotationRequiredState()
- .build())
- .build(),
- Set.of("role"));
-
- private static RealmConfig realmConfigWithEnforcement(boolean enforce) {
- RealmConfig realmConfig = mock(RealmConfig.class);
- when(realmConfig.getConfig(
- FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING))
- .thenReturn(enforce);
- return realmConfig;
- }
-
- @Test
- public void testFlagOff_noException() {
- assertThatCode(
- () ->
- AuthorizationPreConditions.checkCredentialRotationRequired(
- PRINCIPAL,
- PolarisAuthorizableOperation.LIST_CATALOGS,
- realmConfigWithEnforcement(false)))
- .doesNotThrowAnyException();
- }
-
- @Test
- public void testFlagOn_rotateCredentialsOp_noException() {
- assertThatCode(
- () ->
- AuthorizationPreConditions.checkCredentialRotationRequired(
- PRINCIPAL,
- PolarisAuthorizableOperation.ROTATE_CREDENTIALS,
- realmConfigWithEnforcement(true)))
- .doesNotThrowAnyException();
- }
-
- @Test
- public void testFlagOn_nonRotationOp_propertySet_throwsForbidden() {
- assertThatThrownBy(
- () ->
- AuthorizationPreConditions.checkCredentialRotationRequired(
- PRINCIPAL,
- PolarisAuthorizableOperation.LIST_CATALOGS,
- realmConfigWithEnforcement(true)))
- .isInstanceOf(ForbiddenException.class)
- .hasMessageContaining("must rotate credentials");
- }
-
- @Test
- public void testFlagOn_nonRotationOp_propertyNotSet_noException() {
- PolarisPrincipal principal = PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("role"));
-
- assertThatCode(
- () ->
- AuthorizationPreConditions.checkCredentialRotationRequired(
- principal,
- PolarisAuthorizableOperation.LIST_CATALOGS,
- realmConfigWithEnforcement(true)))
- .doesNotThrowAnyException();
- }
-}
diff --git a/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java b/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java
index 2fb0290d83..103c36328b 100644
--- a/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java
+++ b/polaris-core/src/test/java/org/apache/polaris/core/auth/PolarisAuthorizerImplTest.java
@@ -35,8 +35,6 @@ import java.util.Set;
import org.apache.iceberg.exceptions.ForbiddenException;
import org.apache.polaris.core.auth.AuthorizationIntentResolver.ResolvedIntent;
import org.apache.polaris.core.collection.AttributeMap;
-import org.apache.polaris.core.config.FeatureConfiguration;
-import org.apache.polaris.core.config.RealmConfig;
import org.apache.polaris.core.entity.PolarisEntity;
import org.apache.polaris.core.entity.PolarisEntitySubType;
import org.apache.polaris.core.entity.PolarisEntityType;
@@ -52,14 +50,6 @@ import org.mockito.ArgumentMatchers;
public class PolarisAuthorizerImplTest {
- private static RealmConfig realmConfig() {
- RealmConfig realmConfig = mock(RealmConfig.class);
- when(realmConfig.getConfig(
- FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING))
- .thenReturn(false);
- return realmConfig;
- }
-
@ParameterizedTest
@EnumSource(PolarisPrivilege.class)
void subsumingPrivilegesOf(PolarisPrivilege privilege) {
@@ -74,7 +64,7 @@ public class PolarisAuthorizerImplTest {
@Test
void resolveAuthorizationInputsResolvesAll() {
- PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl(realmConfig());
+ PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl();
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisPrincipal principal = PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("role"));
@@ -95,7 +85,7 @@ public class PolarisAuthorizerImplTest {
void authorizeUsesRootTargetForRootGrantRequestWithoutPrimaryTarget() {
// Verify that new authorize SPI call without primary target uses root_container
// for resolution and authorization
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfig()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisResolvedPathWrapper rootWrapper = mock(PolarisResolvedPathWrapper.class);
@@ -138,7 +128,7 @@ public class PolarisAuthorizerImplTest {
void authorizeUsesRootTargetForListCatalogsRequestWithoutPrimaryTarget() {
// Verify that new authorize SPI call without primary target uses root_container
// for resolution and authorization
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfig()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisResolvedPathWrapper rootWrapper = mock(PolarisResolvedPathWrapper.class);
@@ -174,7 +164,7 @@ public class PolarisAuthorizerImplTest {
void authorizeResolvesNamespaceTargetUsingCatalog() {
// Verify authorize call that includes Catalog name in the PolarisSecurable
// successfully resolves the correct namespace
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfig()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisResolvedPathWrapper namespaceWrapper = mock(PolarisResolvedPathWrapper.class);
@@ -217,7 +207,7 @@ public class PolarisAuthorizerImplTest {
@Test
void authorizeSingleOperationMultiIntentRequestEvaluatesSequentially() {
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfig()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisResolvedPathWrapper firstCatalogWrapper = mock(PolarisResolvedPathWrapper.class);
@@ -267,7 +257,7 @@ public class PolarisAuthorizerImplTest {
@Test
void authorizeUpdateTableMultiIntentRequestEvaluatesSequentially() {
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfig()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisResolvedPathWrapper tableWrapper = mock(PolarisResolvedPathWrapper.class);
@@ -329,7 +319,7 @@ public class PolarisAuthorizerImplTest {
@Test
void authorizeReturnsDenyDecision() {
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfig()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
AuthorizationState authzState = new AuthorizationState(manifest);
PolarisResolvedPathWrapper catalogWrapper = mock(PolarisResolvedPathWrapper.class);
@@ -364,7 +354,7 @@ public class PolarisAuthorizerImplTest {
@Test
void authorizeLogsMissingPrivilegeDetailsServerSide() {
- PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl(realmConfigWithDefaults());
+ PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl();
PolarisPrincipal principal = PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("reader"));
PolarisResolvedPathWrapper namespace = resolvedPath(namespaceEntity("ns1"));
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
@@ -401,7 +391,7 @@ public class PolarisAuthorizerImplTest {
// CREATE_TABLE_DIRECT_WITH_WRITE_DELEGATION requires both TABLE_CREATE and TABLE_WRITE_DATA
// on the target namespace. With no grants at all, both should be logged server-side but NOT
// exposed in the client-facing exception.
- PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl(realmConfigWithDefaults());
+ PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl();
PolarisPrincipal principal = PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("reader"));
PolarisResolvedPathWrapper namespace = resolvedPath(namespaceEntity("ns1"));
PolarisResolutionManifest manifest = mock(PolarisResolutionManifest.class);
@@ -437,7 +427,7 @@ public class PolarisAuthorizerImplTest {
// RENAME_TABLE: target privilege TABLE_DROP on the existing table; secondary privileges
// TABLE_LIST and TABLE_CREATE on the destination namespace. Secondary details must NOT
// appear in the client-facing exception.
- PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl(realmConfigWithDefaults());
+ PolarisAuthorizerImpl authorizer = new PolarisAuthorizerImpl();
PolarisPrincipal principal = PolarisPrincipal.of("alice", AttributeMap.EMPTY, Set.of("reader"));
PolarisResolvedPathWrapper srcTable = resolvedPath(tableEntity("src_t"));
PolarisResolvedPathWrapper dstNamespace = resolvedPath(namespaceEntity("dst_ns"));
@@ -483,7 +473,7 @@ public class PolarisAuthorizerImplTest {
void findMissingPrivilegesReturnsEmptyWhenNothingRequiredFails() {
// When the authorizer would say "yes", the helper must return an empty list — this is the
// invariant relied on by isAuthorized.
- PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl(realmConfigWithDefaults()));
+ PolarisAuthorizerImpl authorizer = spy(new PolarisAuthorizerImpl());
PolarisResolvedPathWrapper namespace = resolvedPath(namespaceEntity("ns1"));
// Stub hasTransitivePrivilege to always return true so we exercise the empty-result branch
// of findMissingPrivileges without rebuilding a full grant graph.
@@ -542,16 +532,4 @@ public class PolarisAuthorizerImplTest {
.setName(name)
.build();
}
-
- private static RealmConfig realmConfigWithDefaults() {
- // The credential-rotation-required gate auto-unboxes a Boolean from RealmConfig.getConfig;
- // Mockito's default null answer would NPE before we ever reach the authorization logic.
- // Returning false (the production default for the configuration flag) sends control through
- // to findMissingPrivileges, which is what we want to exercise.
- RealmConfig realmConfig = mock(RealmConfig.class);
- when(realmConfig.getConfig(
- FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING))
- .thenReturn(false);
- return realmConfig;
- }
}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/CredentialRotationFilter.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/CredentialRotationFilter.java
new file mode 100644
index 0000000000..cda73b9318
--- /dev/null
+++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/CredentialRotationFilter.java
@@ -0,0 +1,91 @@
+/*
+ * 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.service.auth;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.SecurityContext;
+import java.lang.reflect.Method;
+import org.apache.iceberg.exceptions.ForbiddenException;
+import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.auth.PolarisPrincipalAttributes;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.RealmConfig;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.entity.PolarisEntityConstants;
+import org.apache.polaris.core.entity.PrincipalEntity;
+import org.apache.polaris.service.admin.api.PolarisPrincipalsApi;
+import org.apache.polaris.service.config.FilterPriorities;
+import org.jboss.resteasy.reactive.server.ServerRequestFilter;
+
+/**
+ * Rejects every request made by a principal that must rotate its credentials first, except the
+ * request that rotates them. The check is independent of the configured authorizer.
+ *
+ * <p>The check is only active when {@link
+ * FeatureConfiguration#ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING} is enabled.
+ */
+public class CredentialRotationFilter {
+
+ /** The generated resource method for the rotate-credentials endpoint. */
+ static final Method ROTATE_CREDENTIALS = rotateCredentialsMethod();
+
+ @Inject RealmConfig realmConfig;
+
+ @ServerRequestFilter(priority = FilterPriorities.CREDENTIAL_ROTATION_FILTER)
+ public void checkCredentialRotationRequired(
+ ContainerRequestContext requestContext, ResourceInfo resourceInfo) {
+ if (requestContext.getSecurityContext().getUserPrincipal() instanceof PolarisPrincipal principal
+ && mustRotateCredentials(principal)
+ && realmConfig.getConfig(
+ FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING)
+ && !isRotateCredentials(resourceInfo)) {
+ throw new ForbiddenException(
+ "Principal '%s' is not authorized because it must rotate credentials first",
+ principal.getName());
+ }
+ }
+
+ private static boolean isRotateCredentials(ResourceInfo resourceInfo) {
+ return ROTATE_CREDENTIALS.equals(resourceInfo.getResourceMethod());
+ }
+
+ private static Method rotateCredentialsMethod() {
+ try {
+ // The method name is derived from the OpenAPI spec operationId
+ return PolarisPrincipalsApi.class.getMethod(
+ "rotateCredentials", String.class, RealmContext.class, SecurityContext.class);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException("Cannot find the rotate-credentials resource method", e);
+ }
+ }
+
+ private static boolean mustRotateCredentials(PolarisPrincipal principal) {
+ return principal
+ .getAttributes()
+ .getOptional(PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY)
+ .map(PrincipalEntity::getInternalPropertiesAsMap)
+ .map(
+ map ->
+ map.containsKey(
+ PolarisEntityConstants.PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_STATE))
+ .orElse(false);
+ }
+}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultPolarisAuthorizerFactory.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultPolarisAuthorizerFactory.java
index 7f11b9ce10..b1bece3ccb 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultPolarisAuthorizerFactory.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultPolarisAuthorizerFactory.java
@@ -32,6 +32,6 @@ class DefaultPolarisAuthorizerFactory implements PolarisAuthorizerFactory {
@Override
public PolarisAuthorizer create(RealmConfig realmConfig) {
- return new PolarisAuthorizerImpl(realmConfig);
+ return new PolarisAuthorizerImpl();
}
}
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java b/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
index 66414d1d40..fdd04997f2 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/config/FilterPriorities.java
@@ -21,10 +21,17 @@ package org.apache.polaris.service.config;
import jakarta.ws.rs.Priorities;
public final class FilterPriorities {
+
+ // Pre-Authentication filters
+
public static final int REQUEST_ID_FILTER = Priorities.AUTHENTICATION - 101;
- public static final int REALM_CONTEXT_FILTER = REQUEST_ID_FILTER + 1;
+ public static final int REALM_CONTEXT_FILTER = Priorities.AUTHENTICATION - 100;
+ public static final int MDC_FILTER = Priorities.AUTHENTICATION - 99;
+ public static final int TRACING_FILTER = Priorities.AUTHENTICATION - 98;
+ public static final int IDEMPOTENCY_KEY_FILTER = Priorities.AUTHENTICATION - 97;
+
+ // Post-Authentication filters
+
+ public static final int CREDENTIAL_ROTATION_FILTER = Priorities.AUTHORIZATION;
public static final int RATE_LIMITER_FILTER = Priorities.USER;
- public static final int MDC_FILTER = REALM_CONTEXT_FILTER + 1;
- public static final int TRACING_FILTER = REALM_CONTEXT_FILTER + 2;
- public static final int IDEMPOTENCY_KEY_FILTER = REALM_CONTEXT_FILTER + 3;
}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
index fde669bd08..05ea435a10 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
@@ -395,7 +395,7 @@ public class ManagementServiceTest {
new UnsafeInMemorySecretsManager(),
new DefaultServiceIdentityProvider(),
principal,
- new PolarisAuthorizerImpl(services.realmConfig()),
+ new PolarisAuthorizerImpl(),
ReservedProperties.NONE,
services.vendingMechanisms());
}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
index db0cc28c5d..438041f5c8 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/admin/PolarisAuthzTestBase.java
@@ -223,7 +223,7 @@ public abstract class PolarisAuthzTestBase {
QuarkusMock.installMockForType(realmContext, RealmContext.class);
polarisContext = callContext.getPolarisCallContext();
- polarisAuthorizer = new PolarisAuthorizerImpl(realmConfig);
+ polarisAuthorizer = new PolarisAuthorizerImpl();
PrincipalEntity rootPrincipal =
metaStoreManager.findRootPrincipal(polarisContext).orElseThrow();
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/CredentialRotationFilterTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/CredentialRotationFilterTest.java
new file mode 100644
index 0000000000..963003478e
--- /dev/null
+++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/CredentialRotationFilterTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.service.auth;
+
+import static org.apache.polaris.service.auth.CredentialRotationFilter.ROTATE_CREDENTIALS;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.container.ResourceInfo;
+import jakarta.ws.rs.core.SecurityContext;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Set;
+import org.apache.iceberg.exceptions.ForbiddenException;
+import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.auth.PolarisPrincipalAttributes;
+import org.apache.polaris.core.collection.AttributeMap;
+import org.apache.polaris.core.collection.ImmutableAttributeMap;
+import org.apache.polaris.core.config.FeatureConfiguration;
+import org.apache.polaris.core.config.RealmConfig;
+import org.apache.polaris.core.entity.PrincipalEntity;
+import org.apache.polaris.service.admin.api.PolarisPrincipalsApi;
+import org.junit.jupiter.api.Test;
+
+class CredentialRotationFilterTest {
+
+ private static final PolarisPrincipal MUST_ROTATE =
+ PolarisPrincipal.of(
+ "alice",
+ ImmutableAttributeMap.builder()
+ .put(
+ PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY,
+ new PrincipalEntity.Builder()
+ .setName("alice")
+ .setCredentialRotationRequiredState()
+ .build())
+ .build(),
+ Set.of("role"));
+
+ private static final PolarisPrincipal REGULAR =
+ PolarisPrincipal.of("bob", AttributeMap.EMPTY, Set.of("role"));
+
+ @Test
+ void rejectsOtherEndpoints() {
+ assertThatThrownBy(() -> filter(true, MUST_ROTATE, listPrincipals()))
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("must rotate credentials first");
+ }
+
+ @Test
+ void allowsRotateCredentialsEndpoint() {
+ assertThatCode(() -> filter(true, MUST_ROTATE, ROTATE_CREDENTIALS)).doesNotThrowAnyException();
+ }
+
+ @Test
+ void allowsWhenEnforcementDisabled() {
+ assertThatCode(() -> filter(false, MUST_ROTATE, listPrincipals())).doesNotThrowAnyException();
+ }
+
+ @Test
+ void allowsPrincipalThatDoesNotNeedToRotate() {
+ assertThatCode(() -> filter(true, REGULAR, listPrincipals())).doesNotThrowAnyException();
+ }
+
+ @Test
+ void ignoresNonPolarisPrincipal() {
+ ContainerRequestContext requestContext = requestContext(mock(Principal.class));
+ assertThatCode(() -> newFilter(true).checkCredentialRotationRequired(requestContext, null))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void ignoresUnauthenticatedRequests() {
+ ContainerRequestContext requestContext = requestContext(null);
+ assertThatCode(() -> newFilter(true).checkCredentialRotationRequired(requestContext, null))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void doesNotInspectResourceWhenEnforcementDisabled() {
+ ContainerRequestContext requestContext = requestContext(MUST_ROTATE);
+ assertThatCode(() -> newFilter(false).checkCredentialRotationRequired(requestContext, null))
+ .doesNotThrowAnyException();
+ }
+
+ private static CredentialRotationFilter newFilter(boolean enforce) {
+ RealmConfig realmConfig = mock(RealmConfig.class);
+ when(realmConfig.getConfig(
+ FeatureConfiguration.ENFORCE_PRINCIPAL_CREDENTIAL_ROTATION_REQUIRED_CHECKING))
+ .thenReturn(enforce);
+ CredentialRotationFilter filter = new CredentialRotationFilter();
+ filter.realmConfig = realmConfig;
+ return filter;
+ }
+
+ private static Method listPrincipals() {
+ return Arrays.stream(PolarisPrincipalsApi.class.getMethods())
+ .filter(m -> m.getName().equals("listPrincipals"))
+ .findFirst()
+ .orElseThrow();
+ }
+
+ private static void filter(boolean enforce, PolarisPrincipal principal, Method method) {
+ ResourceInfo resourceInfo = mock(ResourceInfo.class);
+ when(resourceInfo.getResourceClass()).thenAnswer(i -> PolarisPrincipalsApi.class);
+ when(resourceInfo.getResourceMethod()).thenReturn(method);
+ newFilter(enforce).checkCredentialRotationRequired(requestContext(principal), resourceInfo);
+ }
+
+ private static ContainerRequestContext requestContext(Principal principal) {
+ SecurityContext securityContext = mock(SecurityContext.class);
+ when(securityContext.getUserPrincipal()).thenReturn(principal);
+ ContainerRequestContext requestContext = mock(ContainerRequestContext.class);
+ when(requestContext.getSecurityContext()).thenReturn(securityContext);
+ return requestContext;
+ }
+}
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java
index 03081e2988..5989e6801d 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/generic/AbstractPolarisGenericTableCatalogTest.java
@@ -169,7 +169,7 @@ public abstract class AbstractPolarisGenericTableCatalogTest {
Set.of());
polarisPrincipalHolder.set(authenticatedRoot);
- PolarisAuthorizer authorizer = new PolarisAuthorizerImpl(realmConfig);
+ PolarisAuthorizer authorizer = new PolarisAuthorizerImpl();
ReservedProperties reservedProperties = ReservedProperties.NONE;
adminService =
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java
index 7e5259a1f5..6bf5e2868b 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractIcebergCatalogHandlerAuthzTest.java
@@ -23,14 +23,12 @@ import static org.apache.polaris.service.catalog.AccessDelegationMode.VENDED_CRE
import com.google.common.collect.ImmutableMap;
import jakarta.inject.Inject;
import java.time.Duration;
-import java.time.Instant;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
-import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.iceberg.CatalogProperties;
import org.apache.iceberg.CatalogUtil;
@@ -67,7 +65,6 @@ import org.apache.iceberg.view.ImmutableSQLViewRepresentation;
import org.apache.iceberg.view.ImmutableViewVersion;
import org.apache.polaris.core.admin.model.CreateCatalogRequest;
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
-import org.apache.polaris.core.admin.model.PrincipalWithCredentialsCredentials;
import org.apache.polaris.core.admin.model.StorageConfigInfo;
import org.apache.polaris.core.auth.PolarisPrincipal;
import org.apache.polaris.core.auth.PolarisPrincipalAttributes;
@@ -80,8 +77,6 @@ import org.apache.polaris.core.context.CallContext;
import org.apache.polaris.core.entity.CatalogEntity;
import org.apache.polaris.core.entity.CatalogRoleEntity;
import org.apache.polaris.core.entity.PolarisPrivilege;
-import org.apache.polaris.core.entity.PrincipalEntity;
-import org.apache.polaris.core.persistence.dao.entity.CreatePrincipalResult;
import org.apache.polaris.core.persistence.resolver.PolarisResolutionManifest;
import org.apache.polaris.service.admin.PolarisAuthzTestBase;
import org.apache.polaris.service.admin.PolarisAuthzTestsFactory;
@@ -172,141 +167,6 @@ public abstract class AbstractIcebergCatalogHandlerAuthzTest extends PolarisAuth
.createTests();
}
- @TestFactory
- Stream<DynamicNode> testInsufficientPermissionsPriorToSecretRotation() {
- String principalName = "all_the_powers";
- CreatePrincipalResult newPrincipal =
- metaStoreManager.createPrincipal(
- callContext.getPolarisCallContext(),
- new PrincipalEntity.Builder()
- .setName(principalName)
- .setCreateTimestamp(Instant.now().toEpochMilli())
- .setCredentialRotationRequiredState()
- .build());
- newRootAdminService().assignPrincipalRole(principalName, PRINCIPAL_ROLE1);
- newRootAdminService().assignPrincipalRole(principalName, PRINCIPAL_ROLE2);
-
- PrincipalEntity principalEntity = newPrincipal.getPrincipal();
- PolarisPrincipal authenticatedPrincipal =
- PolarisPrincipal.of(
- principalEntity.getName(),
- ImmutableAttributeMap.builder()
- .put(PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY, principalEntity)
- .build(),
- Set.of(PRINCIPAL_ROLE1, PRINCIPAL_ROLE2));
-
- Supplier<IcebergCatalogHandler> handler =
- () ->
- ImmutableIcebergCatalogHandler.builder()
- .from(
- icebergCatalogHandlerFactory.createHandler(
- CATALOG_NAME, authenticatedPrincipal))
- .build();
-
- // a variety of actions are all disallowed because the principal's credentials must be rotated
-
- Namespace ns3 = Namespace.of("ns3");
- Stream<DynamicNode> beforeRotationTests =
- Stream.of(
- authzTestsBuilder("listNamespaces (before rotation)")
- .action(() -> handler.get().listNamespaces(Namespace.of(), null, null))
- .principalName(principalName)
- .shouldFailWithAnyPrivilege()
- .createTests(),
- authzTestsBuilder("createNamespace (before rotation)")
- .action(
- () ->
- handler
- .get()
- .createNamespace(
- CreateNamespaceRequest.builder().withNamespace(ns3).build()))
- .principalName(principalName)
- .shouldFailWithAnyPrivilege()
- .createTests(),
- authzTestsBuilder("listTables (before rotation)")
- .action(() -> handler.get().listTables(NS1, null, null))
- .principalName(principalName)
- .shouldFailWithAnyPrivilege()
- .createTests())
- .flatMap(s -> s);
-
- // Rotate credentials and create refreshed wrapper
- PrincipalWithCredentialsCredentials credentials =
- new PrincipalWithCredentialsCredentials(
- newPrincipal.getPrincipalSecrets().getPrincipalClientId(),
- newPrincipal.getPrincipalSecrets().getMainSecret());
- PrincipalEntity refreshPrincipal =
- rotateAndRefreshPrincipal(
- metaStoreManager, principalName, credentials, callContext.getPolarisCallContext());
- PolarisPrincipal authenticatedPrincipal1 =
- PolarisPrincipal.of(
- refreshPrincipal.getName(),
- ImmutableAttributeMap.builder()
- .put(PolarisPrincipalAttributes.PRINCIPAL_ENTITY_ATTRIBUTE_KEY, refreshPrincipal)
- .build(),
- Set.of(PRINCIPAL_ROLE1, PRINCIPAL_ROLE2));
-
- Supplier<IcebergCatalogHandler> refreshedWrapper =
- () ->
- ImmutableIcebergCatalogHandler.builder()
- .from(
- icebergCatalogHandlerFactory.createHandler(
- CATALOG_NAME, authenticatedPrincipal1))
- .polarisPrincipal(authenticatedPrincipal1)
- .build();
-
- // Grant NAMESPACE_DROP to CATALOG_ROLE2 so cleanup can work
- assertSuccess(
- newRootAdminService()
- .grantPrivilegeOnCatalogToRole(
- CATALOG_NAME, CATALOG_ROLE2, PolarisPrivilege.NAMESPACE_DROP));
-
- // Tests after credential rotation - actions should succeed with proper privileges
- Stream<DynamicNode> afterRotationTests =
- Stream.of(
- authzTestsBuilder("listNamespaces (after rotation)")
- .action(() -> refreshedWrapper.get().listNamespaces(Namespace.of(), null, null))
- .principalName(principalName)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_LIST)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_CREATE)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_READ_PROPERTIES)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_WRITE_PROPERTIES)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_FULL_METADATA)
- .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_CONTENT)
- .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_METADATA)
- .createTests(),
- authzTestsBuilder("createNamespace (after rotation)")
- .action(
- () ->
- refreshedWrapper
- .get()
- .createNamespace(
- CreateNamespaceRequest.builder().withNamespace(ns3).build()))
- .cleanupAction(() -> newHandler(Set.of(PRINCIPAL_ROLE2)).dropNamespace(ns3))
- .principalName(principalName)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_CREATE)
- .shouldPassWith(PolarisPrivilege.NAMESPACE_FULL_METADATA)
- .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_CONTENT)
- .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_METADATA)
- .createTests(),
- authzTestsBuilder("listTables (after rotation)")
- .action(() -> refreshedWrapper.get().listTables(NS1, null, null))
- .principalName(principalName)
- .shouldPassWith(PolarisPrivilege.TABLE_LIST)
- .shouldPassWith(PolarisPrivilege.TABLE_CREATE)
- .shouldPassWith(PolarisPrivilege.TABLE_READ_PROPERTIES)
- .shouldPassWith(PolarisPrivilege.TABLE_WRITE_PROPERTIES)
- .shouldPassWith(PolarisPrivilege.TABLE_READ_DATA)
- .shouldPassWith(PolarisPrivilege.TABLE_WRITE_DATA)
- .shouldPassWith(PolarisPrivilege.TABLE_FULL_METADATA)
- .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_CONTENT)
- .shouldPassWith(PolarisPrivilege.CATALOG_MANAGE_METADATA)
- .createTests())
- .flatMap(s -> s);
-
- return Stream.concat(beforeRotationTests, afterRotationTests);
- }
-
@Test
public void testListNamespacesCatalogLevelWithPrincipalRoleActivation() {
// Grant catalog-level privilege to CATALOG_ROLE1
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java
index c1bd488a91..9370bc2f18 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/AbstractLocalIcebergCatalogViewTest.java
@@ -178,7 +178,7 @@ public abstract class AbstractLocalIcebergCatalogViewTest
.build(),
Set.of());
- authorizer = new PolarisAuthorizerImpl(realmConfig);
+ authorizer = new PolarisAuthorizerImpl();
reservedProperties = ReservedProperties.NONE;
newAdminService()
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java
index aea0b8c235..491a84b824 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/catalog/policy/AbstractPolicyCatalogTest.java
@@ -194,7 +194,7 @@ public abstract class AbstractPolicyCatalogTest {
Set.of());
polarisPrincipalHolder.set(authenticatedRoot);
- PolarisAuthorizer authorizer = new PolarisAuthorizerImpl(realmConfig);
+ PolarisAuthorizer authorizer = new PolarisAuthorizerImpl();
ReservedProperties reservedProperties = ReservedProperties.NONE;
adminService =