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 d1d359a78 Remove ActiveRolesProvider (#2390)
d1d359a78 is described below

commit d1d359a7897d05addaedde8be30fd44b947f57aa
Author: Alexandre Dutra <adu...@apache.org>
AuthorDate: Fri Sep 19 18:36:59 2025 +0200

    Remove ActiveRolesProvider (#2390)
    
    Summary of changes:
    
    - As proposed on the ML, `ActiveRolesProvider` is removed, and 
`DefaultActiveRolesProvider` is merged into `DefaultAuthenticator`. 
`ActiveRolesAugmentor` is also merged into `AuthenticatingAugmentor`.
    
    - The implicit convention that no roles in credentials == all roles 
requested is removed as it is ambiguous. Credentials must explicitly include 
the `PRINCIPAL_ROLE:ALL` pseudo-role to request all roles available.
    
    - PersistedPolarisPrincipal is removed. It existed merely as a means of 
passing the `PrincipalEntity` from the authenticator to the roles provider. 
This is not necessary anymore.
---
 CHANGELOG.md                                       |   3 +-
 helm/polaris/README.md                             |   5 +-
 helm/polaris/ci/authentication-values.yaml         |   2 -
 helm/polaris/templates/_helpers.tpl                |   1 -
 helm/polaris/tests/configmap_test.yaml             |  16 +-
 helm/polaris/values.yaml                           |   5 +-
 .../src/main/resources/application.properties      |   5 -
 .../polaris/service/auth/ActiveRolesAugmentor.java |  80 ------
 .../polaris/service/auth/ActiveRolesProvider.java  |  37 ---
 .../service/auth/AuthenticatingAugmentor.java      |  12 +-
 .../auth/AuthenticationRealmConfiguration.java     |  20 +-
 .../service/auth/DefaultActiveRolesProvider.java   | 120 --------
 .../polaris/service/auth/DefaultAuthenticator.java | 192 +++++++++++--
 .../service/auth/PersistedPolarisPrincipal.java    |  60 ----
 .../polaris/service/config/ServiceProducers.java   |  11 -
 .../service/auth/ActiveRolesAugmentorTest.java     | 117 --------
 .../service/auth/DefaultAuthenticatorTest.java     | 306 ++++++++++++++++++---
 site/content/in-dev/unreleased/external-idp.md     |  23 +-
 18 files changed, 456 insertions(+), 559 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e96c8520d..b2063241b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -101,8 +101,7 @@ refresh-credentials flag for the desired storage provider.
 
 ### Deprecations
 
-- The property `polaris.active-roles-provider.type` is deprecated for removal.
-- The `ActiveRolesProvider` interface is deprecated for removal.
+* The property `polaris.active-roles-provider.type` is deprecated and has no 
effect anymore.
 
 ### Fixes
 
diff --git a/helm/polaris/README.md b/helm/polaris/README.md
index 61103e6bf..bbfc8557d 100644
--- a/helm/polaris/README.md
+++ b/helm/polaris/README.md
@@ -189,8 +189,7 @@ ct install --namespace polaris --charts ./helm/polaris
 |-----|------|---------|-------------|
 | advancedConfig | object | `{}` | Advanced configuration. You can pass here 
any valid Polaris or Quarkus configuration property. Any property that is 
defined here takes precedence over all the other configuration values generated 
by this chart. Properties can be passed "flattened" or as nested YAML objects 
(see examples below). Note: values should be strings; avoid using numbers, 
booleans, or other types. |
 | affinity | object | `{}` | Affinity and anti-affinity for polaris pods. See 
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity.
 |
-| authentication | object | 
`{"activeRolesProvider":{"type":"default"},"authenticator":{"type":"default"},"realmOverrides":{},"tokenBroker":{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"},"tokenService":{"type":"default"},"type":"internal"}`
 | Polaris authentication config [...]
-| authentication.activeRolesProvider | object | `{"type":"default"}` | The 
`ActiveRolesProvider` implementation to use. Only one built-in type is 
supported: default. |
+| authentication | object | 
`{"authenticator":{"type":"default"},"realmOverrides":{},"tokenBroker":{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"},"tokenService":{"type":"default"},"type":"internal"}`
 | Polaris authentication configuration. |
 | authentication.authenticator | object | `{"type":"default"}` | The 
`Authenticator` implementation to use. Only one built-in type is supported: 
default. |
 | authentication.realmOverrides | object | `{}` | Authentication configuration 
overrides per realm. |
 | authentication.tokenBroker | object | 
`{"maxTokenGeneration":"PT1H","secret":{"name":null,"privateKey":"private.pem","publicKey":"public.pem","rsaKeyPair":{"privateKey":"private.pem","publicKey":"public.pem"},"secretKey":"symmetric.pem","symmetricKey":{"secretKey":"symmetric.key"}},"type":"rsa-key-pair"}`
 | The `TokenBroker` implementation to use. Two built-in types are supported: 
rsa-key-pair and symmetric-key. Only relevant when using internal (or mixed) 
authentication. When using ex [...]
@@ -299,7 +298,7 @@ ct install --namespace polaris --charts ./helm/polaris
 | oidc.principalMapper.type | string | `"default"` | The `PrincipalMapper` 
implementation to use. Only one built-in type is supported: default. |
 | oidc.principalRolesMapper | object | 
`{"filter":null,"mappings":[],"rolesClaimPath":null,"type":"default"}` | 
Principal roles mapping configuration. |
 | oidc.principalRolesMapper.filter | string | `nil` | A regular expression 
that matches the role names in the identity. Only roles that match this regex 
will be included in the Polaris-specific roles. |
-| oidc.principalRolesMapper.mappings | list | `[]` | A list of regex mappings 
that will be applied to each role name in the identity. This can be used to 
transform the role names in the identity into role names as expected by 
Polaris. The default ActiveRolesProvider expects the security identity to 
expose role names in the format `POLARIS_ROLE:<role name>`. |
+| oidc.principalRolesMapper.mappings | list | `[]` | A list of regex mappings 
that will be applied to each role name in the identity. This can be used to 
transform the role names in the identity into role names as expected by 
Polaris. The default Authenticator expects the security identity to expose role 
names in the format `POLARIS_ROLE:<role name>`. |
 | oidc.principalRolesMapper.rolesClaimPath | string | `nil` | The path to the 
claim that contains the principal roles. Nested paths can be expressed using 
"/" as a separator, e.g. "polaris/principal_roles" would look for the 
"principal_roles" field inside the "polaris" object in the token claims. If not 
set, Quarkus looks for roles in standard locations. See 
https://quarkus.io/guides/security-oidc-bearer-token-authentication#token-claims-and-security-identity-roles.
 |
 | oidc.principalRolesMapper.type | string | `"default"` | The 
`PrincipalRolesMapper` implementation to use. Only one built-in type is 
supported: default. |
 | persistence | object | 
`{"relationalJdbc":{"secret":{"jdbcUrl":"jdbcUrl","name":null,"password":"password","username":"username"}},"type":"in-memory"}`
 | Polaris persistence configuration. |
diff --git a/helm/polaris/ci/authentication-values.yaml 
b/helm/polaris/ci/authentication-values.yaml
index 1087c2c44..7ee9fe410 100644
--- a/helm/polaris/ci/authentication-values.yaml
+++ b/helm/polaris/ci/authentication-values.yaml
@@ -31,8 +31,6 @@ authentication:
   type: internal
   authenticator:
     type: default
-  activeRolesProvider:
-    type: default
   tokenService:
     type: default
   tokenBroker:
diff --git a/helm/polaris/templates/_helpers.tpl 
b/helm/polaris/templates/_helpers.tpl
index af0b596e3..2089a95d2 100644
--- a/helm/polaris/templates/_helpers.tpl
+++ b/helm/polaris/templates/_helpers.tpl
@@ -316,7 +316,6 @@ Sets the configmap authentication options for a given realm.
 {{- end -}}
 {{- $_ := set $map (printf "%s.type" $prefix) $authType -}}
 {{- $_ = set $map (printf "%s.authenticator.type" $prefix) (dig 
"authenticator" "type" "default" $auth) -}}
-{{- $_ = set $map (printf "%s.active-roles-provider.type" $prefix) (dig 
"activeRolesProvider" "type" "default" $auth) -}}
 {{- if (or (eq $authType "mixed") (eq $authType "internal")) -}}
 {{- $tokenBrokerType := dig "tokenBroker" "type" "rsa-key-pair" $auth -}}
 {{- $_ = set $map (printf "%s.token-service.type" $prefix) (dig "tokenService" 
"type" "default" $auth) -}}
diff --git a/helm/polaris/tests/configmap_test.yaml 
b/helm/polaris/tests/configmap_test.yaml
index eb6177076..cc0c1354d 100644
--- a/helm/polaris/tests/configmap_test.yaml
+++ b/helm/polaris/tests/configmap_test.yaml
@@ -168,16 +168,6 @@ tests:
     asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.authenticator.type=custom" }
 
-  - it: should configure default active roles provider
-    set: { authentication: { activeRolesProvider: { type: default } } }
-    asserts:
-      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.active-roles-provider.type=default" }
-
-  - it: should configure custom active roles provider
-    set: { authentication: { activeRolesProvider: { type: custom } } }
-    asserts:
-      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.active-roles-provider.type=custom" }
-
   - it: should configure default token service
     set: { authentication: { tokenService: { type: default } } }
     asserts:
@@ -209,18 +199,16 @@ tests:
     set:
       authentication:
         realmOverrides:
-          realm1: { type: mixed, authenticator: { type: custom1 }, 
activeRolesProvider: { type: custom1 }, tokenBroker: { type: custom1 }, 
tokenService: { type: custom1 } }
-          realm2: { type: external, authenticator: { type: custom2 }, 
activeRolesProvider: { type: custom2 } }
+          realm1: { type: mixed, authenticator: { type: custom1 }, 
tokenBroker: { type: custom1 }, tokenService: { type: custom1 } }
+          realm2: { type: external, authenticator: { type: custom2 } }
           "REALM 3": { type: internal, tokenBroker: { type: rsa-key-pair, 
secret: { name: polaris-auth } } }
     asserts:
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".type=mixed" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".authenticator.type=custom1" }
-      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".active-roles-provider.type=custom1" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".token-broker.type=custom1" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm1\".token-service.type=custom1" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm2\".type=external" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm2\".authenticator.type=custom2" }
-      - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"realm2\".active-roles-provider.type=custom2" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 3\".type=internal" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 3\".token-broker.type=rsa-key-pair" }
       - matchRegex: { path: 'data["application.properties"]', pattern: 
"polaris.authentication.\"REALM\\\\ 
3\".token-broker.rsa-key-pair.public-key-file=/deployments/config/REALM\\+3/public.pem"
 }
diff --git a/helm/polaris/values.yaml b/helm/polaris/values.yaml
index fddd9cc4e..9cecf0a27 100644
--- a/helm/polaris/values.yaml
+++ b/helm/polaris/values.yaml
@@ -580,9 +580,6 @@ authentication:
   # -- The `Authenticator` implementation to use. Only one built-in type is 
supported: default.
   authenticator:
     type: default
-  # -- The `ActiveRolesProvider` implementation to use. Only one built-in type 
is supported: default.
-  activeRolesProvider:
-    type: default
   # -- The token service (`IcebergRestOAuth2ApiService`) implementation to 
use. Two built-in types are supported: default and disabled.
   # Only relevant when using internal (or mixed) authentication. When using 
external authentication, the token service is always disabled.
   tokenService:
@@ -671,7 +668,7 @@ oidc:
     filter: ~  # ^(?!profile$|email$).*
     # -- A list of regex mappings that will be applied to each role name in 
the identity. This can
     # be used to transform the role names in the identity into role names as 
expected by Polaris.
-    # The default ActiveRolesProvider expects the security identity to expose 
role names in the
+    # The default Authenticator expects the security identity to expose role 
names in the
     # format `POLARIS_ROLE:<role name>`.
     mappings: []
       # - regex: role_(.*)
diff --git a/runtime/defaults/src/main/resources/application.properties 
b/runtime/defaults/src/main/resources/application.properties
index a521e8277..2eb7e8592 100644
--- a/runtime/defaults/src/main/resources/application.properties
+++ b/runtime/defaults/src/main/resources/application.properties
@@ -150,17 +150,12 @@ polaris.rate-limiter.token-bucket.type=default
 polaris.rate-limiter.token-bucket.requests-per-second=9999
 polaris.rate-limiter.token-bucket.window=PT10S
 
-# This property is DEPRECATED for removal; use 
polaris.authentication.active-roles-provider.type instead
-polaris.active-roles-provider.type=default
-
 # Polaris authentication settings
 polaris.authentication.type=internal
 polaris.authentication.authenticator.type=default
-polaris.authentication.active-roles-provider.type=${polaris.active-roles-provider.type}
 # Per-realm overrides:
 # polaris.authentication.realm1.type=external
 # polaris.authentication.realm1.authenticator.type=custom
-# polaris.authentication.realm1.active-roles-provider.type=custom
 
 # Options effective when using internal auth (can be overridden in per realm):
 polaris.authentication.token-service.type=default
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/ActiveRolesAugmentor.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/ActiveRolesAugmentor.java
deleted file mode 100644
index 811ddbac7..000000000
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/ActiveRolesAugmentor.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.service.auth;
-
-import io.quarkus.security.AuthenticationFailedException;
-import io.quarkus.security.identity.AuthenticationRequestContext;
-import io.quarkus.security.identity.SecurityIdentity;
-import io.quarkus.security.identity.SecurityIdentityAugmentor;
-import io.quarkus.security.runtime.QuarkusSecurityIdentity;
-import io.smallrye.mutiny.Uni;
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import java.util.Set;
-import org.apache.polaris.core.auth.PolarisPrincipal;
-
-/**
- * A custom {@link SecurityIdentityAugmentor} that adds active roles to the 
{@link
- * SecurityIdentity}. This is used to augment the identity with valid active 
roles after
- * authentication.
- */
-@ApplicationScoped
-@Deprecated
-public class ActiveRolesAugmentor implements SecurityIdentityAugmentor {
-
-  // must run after AuthenticatingAugmentor
-  public static final int PRIORITY = AuthenticatingAugmentor.PRIORITY - 1;
-
-  private final ActiveRolesProvider activeRolesProvider;
-
-  @Inject
-  public ActiveRolesAugmentor(ActiveRolesProvider activeRolesProvider) {
-    this.activeRolesProvider = activeRolesProvider;
-  }
-
-  @Override
-  public int priority() {
-    return PRIORITY;
-  }
-
-  @Override
-  public Uni<SecurityIdentity> augment(
-      SecurityIdentity identity, AuthenticationRequestContext context) {
-    if (identity.isAnonymous()) {
-      return Uni.createFrom().item(identity);
-    }
-    return context.runBlocking(() -> validateActiveRoles(identity));
-  }
-
-  private SecurityIdentity validateActiveRoles(SecurityIdentity identity) {
-    if (!(identity.getPrincipal() instanceof PolarisPrincipal)) {
-      throw new AuthenticationFailedException("No Polaris principal found");
-    }
-    PolarisPrincipal polarisPrincipal = 
identity.getPrincipal(PolarisPrincipal.class);
-    Set<String> validRoleNames = 
activeRolesProvider.getActiveRoles(polarisPrincipal);
-    return QuarkusSecurityIdentity.builder()
-        .setAnonymous(false)
-        .setPrincipal(polarisPrincipal)
-        .addRoles(validRoleNames) // replace the current roles with valid ones
-        .addCredentials(identity.getCredentials())
-        .addAttributes(identity.getAttributes())
-        .addPermissionChecker(identity::checkPermission)
-        .build();
-  }
-}
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java
deleted file mode 100644
index effbb834b..000000000
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java
+++ /dev/null
@@ -1,37 +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.service.auth;
-
-import java.util.Set;
-import org.apache.polaris.core.auth.PolarisPrincipal;
-
-/**
- * Provides the active roles for a given principal. Implementations may rely 
on the active request
- * or SecurityContext to determine the active roles.
- */
-@Deprecated
-public interface ActiveRolesProvider {
-  /**
-   * Returns the active roles for the given principal.
-   *
-   * @param principal the currently authenticated principal
-   * @return the active roles
-   */
-  Set<String> getActiveRoles(PolarisPrincipal principal);
-}
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticatingAugmentor.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticatingAugmentor.java
index 3bff00b5c..50b334da2 100644
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticatingAugmentor.java
+++ 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticatingAugmentor.java
@@ -72,7 +72,17 @@ public class AuthenticatingAugmentor implements 
SecurityIdentityAugmentor {
       SecurityIdentity identity, PolarisCredential polarisCredential) {
     try {
       PolarisPrincipal polarisPrincipal = 
authenticator.authenticate(polarisCredential);
-      return 
QuarkusSecurityIdentity.builder(identity).setPrincipal(polarisPrincipal).build();
+      QuarkusSecurityIdentity.Builder builder =
+          QuarkusSecurityIdentity.builder()
+              .setAnonymous(false)
+              .setPrincipal(polarisPrincipal)
+              .addRoles(polarisPrincipal.getRoles())
+              .addCredentials(identity.getCredentials())
+              .addAttributes(identity.getAttributes())
+              .addPermissionChecker(identity::checkPermission);
+      // Also include the Polaris principal properties as attributes of the 
identity
+      polarisPrincipal.getProperties().forEach(builder::addAttribute);
+      return builder.build();
     } catch (RuntimeException e) {
       throw new AuthenticationFailedException(e);
     }
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java
index 716ab6d1d..0db938b43 100644
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java
+++ 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java
@@ -31,7 +31,8 @@ public interface AuthenticationRealmConfiguration {
 
   /**
    * The configuration for the authenticator. The authenticator is responsible 
for validating token
-   * credentials and mapping those credentials to an existing Polaris 
principal.
+   * credentials and mapping those credentials to an existing principal and 
validated principal
+   * roles.
    */
   AuthenticatorConfiguration authenticator();
 
@@ -45,23 +46,6 @@ public interface AuthenticationRealmConfiguration {
     String type();
   }
 
-  /**
-   * The configuration for the active roles provider. The active roles 
provider is responsible for
-   * determining the active roles for a given Polaris principal.
-   */
-  @Deprecated
-  ActiveRolesProviderConfiguration activeRolesProvider();
-
-  interface ActiveRolesProviderConfiguration {
-
-    /**
-     * The type of the active roles provider. Must be a registered {@link
-     * org.apache.polaris.service.auth.ActiveRolesProvider} identifier.
-     */
-    @WithDefault("default")
-    String type();
-  }
-
   /**
    * The configuration for the OAuth2 service that delivers OAuth2 tokens. 
Only relevant when using
    * internal authentication (using Polaris as the authorization server).
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultActiveRolesProvider.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultActiveRolesProvider.java
deleted file mode 100644
index c5596b91f..000000000
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultActiveRolesProvider.java
+++ /dev/null
@@ -1,120 +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.service.auth;
-
-import com.google.common.base.Throwables;
-import io.smallrye.common.annotation.Identifier;
-import jakarta.enterprise.context.RequestScoped;
-import jakarta.inject.Inject;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import org.apache.iceberg.exceptions.NotAuthorizedException;
-import org.apache.iceberg.exceptions.ServiceFailureException;
-import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.PolarisDiagnostics;
-import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.apache.polaris.core.context.CallContext;
-import org.apache.polaris.core.entity.PolarisEntity;
-import org.apache.polaris.core.entity.PolarisEntityType;
-import org.apache.polaris.core.entity.PrincipalRoleEntity;
-import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
-import org.apache.polaris.core.persistence.dao.entity.EntityResult;
-import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Default implementation of the {@link ActiveRolesProvider} looks up the 
grant records for a
- * principal to determine roles that are available. {@link 
PolarisPrincipal#getRoles()} is used to
- * determine which of the available roles are active for this request.
- */
-@RequestScoped
-@Identifier("default")
-@Deprecated
-public class DefaultActiveRolesProvider implements ActiveRolesProvider {
-  private static final Logger LOGGER = 
LoggerFactory.getLogger(DefaultActiveRolesProvider.class);
-
-  @Inject PolarisDiagnostics diagnostics;
-  @Inject CallContext callContext;
-  @Inject PolarisMetaStoreManager metaStoreManager;
-
-  @Override
-  public Set<String> getActiveRoles(PolarisPrincipal principal) {
-    if (!(principal instanceof PersistedPolarisPrincipal 
persistedPolarisPrincipal)) {
-      LOGGER.error(
-          "Expected an PersistedPolarisPrincipal, but got {}: {}",
-          principal.getClass().getName(),
-          Throwables.getStackTraceAsString(new 
ServiceFailureException("Invalid principal type")));
-      throw new NotAuthorizedException("Unable to authenticate");
-    }
-    List<PrincipalRoleEntity> activeRoles =
-        loadActivePrincipalRoles(
-            principal.getRoles(), persistedPolarisPrincipal.getEntity(), 
metaStoreManager);
-    return 
activeRoles.stream().map(PrincipalRoleEntity::getName).collect(Collectors.toSet());
-  }
-
-  protected List<PrincipalRoleEntity> loadActivePrincipalRoles(
-      Set<String> tokenRoles, PolarisEntity principal, PolarisMetaStoreManager 
metaStoreManager) {
-    PolarisCallContext polarisContext = callContext.getPolarisCallContext();
-    LoadGrantsResult principalGrantResults =
-        metaStoreManager.loadGrantsToGrantee(polarisContext, principal);
-    diagnostics.check(
-        principalGrantResults.isSuccess(),
-        "Failed to resolve principal roles for principal name={} id={}",
-        principal.getName(),
-        principal.getId());
-    if (!principalGrantResults.isSuccess()) {
-      LOGGER.warn(
-          "Failed to resolve principal roles for principal name={} id={}",
-          principal.getName(),
-          principal.getId());
-      throw new NotAuthorizedException("Unable to authenticate");
-    }
-
-    // FIXME how to distinguish allRoles from no roles at all?
-    boolean allRoles = tokenRoles.isEmpty();
-    Predicate<PrincipalRoleEntity> includeRoleFilter =
-        allRoles ? r -> true : r -> tokenRoles.contains(r.getName());
-    List<PrincipalRoleEntity> activeRoles =
-        principalGrantResults.getGrantRecords().stream()
-            .map(
-                gr ->
-                    metaStoreManager.loadEntity(
-                        polarisContext,
-                        gr.getSecurableCatalogId(),
-                        gr.getSecurableId(),
-                        PolarisEntityType.PRINCIPAL_ROLE))
-            .filter(EntityResult::isSuccess)
-            .map(EntityResult::getEntity)
-            .map(PrincipalRoleEntity::of)
-            .filter(includeRoleFilter)
-            .toList();
-    if (activeRoles.size() != principalGrantResults.getGrantRecords().size()) {
-      LOGGER
-          .atWarn()
-          .addKeyValue("principal", principal.getName())
-          .addKeyValue("scopes", tokenRoles)
-          .addKeyValue("roles", activeRoles)
-          .log("Some principal roles were not found in the principal's 
grants");
-    }
-    return activeRoles;
-  }
-}
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java
index e36deec7f..9abc75e38 100644
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java
+++ 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java
@@ -22,51 +22,107 @@ import com.google.common.base.Throwables;
 import io.smallrye.common.annotation.Identifier;
 import jakarta.enterprise.context.RequestScoped;
 import jakarta.inject.Inject;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import org.apache.iceberg.exceptions.NotAuthorizedException;
 import org.apache.iceberg.exceptions.ServiceFailureException;
+import org.apache.polaris.core.PolarisCallContext;
+import org.apache.polaris.core.PolarisDiagnostics;
 import org.apache.polaris.core.auth.PolarisPrincipal;
 import org.apache.polaris.core.context.CallContext;
-import org.apache.polaris.core.entity.PolarisEntity;
 import org.apache.polaris.core.entity.PolarisEntityType;
 import org.apache.polaris.core.entity.PrincipalEntity;
+import org.apache.polaris.core.entity.PrincipalRoleEntity;
 import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.dao.entity.EntityResult;
+import org.apache.polaris.core.persistence.dao.entity.LoadGrantsResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
  * The default {@link Authenticator}.
  *
- * <p>This authenticator is used in both internal and external authentication 
scenarios.
+ * <p>This implementation resolves the principal entity based on the provided 
credentials, extracts
+ * the requested roles, and loads the principal's grants to determine which 
roles are currently
+ * active for the principal.
+ *
+ * <p>It supports a pseudo-role {@value #PRINCIPAL_ROLE_ALL} that allows 
requesting all roles the
+ * principal has been granted in the system.
+ *
+ * <p><strong>This authenticator is used in both internal and external 
authentication scenarios. For
+ * now, it does not support federated principals that are not managed by 
Polaris.</strong>
  */
 @RequestScoped
 @Identifier("default")
 public class DefaultAuthenticator implements Authenticator {
 
+  /**
+   * The pseudo-role that resolves to all roles the principal has been granted 
in the system.
+   *
+   * <p>This role is not a valid role and is not stored in the database; it 
may be used in incoming
+   * credentials to explicitly indicate that the principal is requesting that 
all its roles be
+   * activated upon authentication, without needing to specify each role 
individually.
+   */
   public static final String PRINCIPAL_ROLE_ALL = "PRINCIPAL_ROLE:ALL";
+
+  /**
+   * The prefix for the roles in incoming credentials that are used to 
indicate that the principal
+   * is requesting that specific roles be activated upon authentication.
+   *
+   * <p>If the incoming credentials contain roles prefixed with this string, 
the authenticator will
+   * attempt to resolve those roles from the principal's grants. Only those 
roles will be activated.
+   *
+   * <p>If the incoming credentials contain roles that do not match this 
prefix, they will be
+   * ignored during the authentication process. If necessary, use {@code 
PrincipalRolesMapper} to
+   * convert roles from the identity to Polaris-specific roles.
+   */
   public static final String PRINCIPAL_ROLE_PREFIX = "PRINCIPAL_ROLE:";
 
   private static final Logger LOGGER = 
LoggerFactory.getLogger(DefaultAuthenticator.class);
 
-  @Inject CallContext callContext;
+  private static final Set<String> ALL_ROLES_REQUESTED = Set.of();
+
   @Inject PolarisMetaStoreManager metaStoreManager;
+  @Inject CallContext callContext;
+  @Inject PolarisDiagnostics diagnostics;
 
   @Override
   public PolarisPrincipal authenticate(PolarisCredential credentials) {
-    LOGGER.debug("Resolving principal for credentials={}", credentials);
-    PolarisEntity principal = null;
+
+    LOGGER.debug("Resolving principal for credentials: {}", credentials);
+
+    PrincipalEntity principalEntity = resolvePrincipalEntity(credentials);
+    Set<String> principalRoles = resolvePrincipalRoles(credentials, 
principalEntity);
+    PolarisPrincipal polarisPrincipal = PolarisPrincipal.of(principalEntity, 
principalRoles);
+
+    LOGGER.debug("Resolved principal: {}", polarisPrincipal);
+    return polarisPrincipal;
+  }
+
+  /**
+   * Resolves the principal entity based on the provided credentials.
+   *
+   * <p>This method attempts to load the principal entity using either the 
principal ID or the
+   * principal name from the credentials. If neither is available, nor if the 
principal entity can
+   * be found, it throws a {@link NotAuthorizedException}.
+   */
+  protected PrincipalEntity resolvePrincipalEntity(PolarisCredential 
credentials) {
+
+    PrincipalEntity principal = null;
     try {
       // If the principal id is present, prefer to use it to load the 
principal entity,
       // otherwise, use the principal name to load the entity.
       if (credentials.getPrincipalId() != null && credentials.getPrincipalId() 
> 0) {
         principal =
-            PolarisEntity.of(
-                metaStoreManager.loadEntity(
-                    callContext.getPolarisCallContext(),
-                    0L,
-                    credentials.getPrincipalId(),
-                    PolarisEntityType.PRINCIPAL));
+            PrincipalEntity.of(
+                metaStoreManager
+                    .loadEntity(
+                        callContext.getPolarisCallContext(),
+                        0L,
+                        credentials.getPrincipalId(),
+                        PolarisEntityType.PRINCIPAL)
+                    .getEntity());
       } else if (credentials.getPrincipalName() != null) {
         principal =
             metaStoreManager
@@ -82,26 +138,116 @@ public class DefaultAuthenticator implements 
Authenticator {
           .log("Unable to authenticate user with token");
       throw new ServiceFailureException("Unable to fetch principal entity");
     }
+
     if (principal == null || principal.getType() != 
PolarisEntityType.PRINCIPAL) {
       LOGGER.warn("Failed to resolve principal from credentials={}", 
credentials);
       throw new NotAuthorizedException("Unable to authenticate");
     }
 
-    LOGGER.debug("Resolved principal: {}", principal);
+    return principal;
+  }
+
+  /**
+   * Resolves the roles for the given principal based on the provided 
credentials.
+   *
+   * <p>This method checks the credentials for requested roles and loads the 
principal's grants to
+   * determine which roles are currently active for the principal.
+   *
+   * <p>The returned set of roles will include only those roles that the 
principal has been granted
+   * and that match the requested roles from the credentials. If the 
credentials contain the
+   * pseudo-role {@link #PRINCIPAL_ROLE_ALL}, it indicates that the principal 
is requesting all
+   * roles they have been granted in the system, and all such roles will be 
included in the returned
+   * set.
+   */
+  protected Set<String> resolvePrincipalRoles(
+      PolarisCredential credentials, PrincipalEntity principal) {
+
+    Set<String> requestedRoles = extractRequestedRoles(credentials);
+    LoadGrantsResult loadGrantsResult = loadPrincipalGrants(principal);
+
+    Predicate<String> includeRoleFilter =
+        requestedRoles == ALL_ROLES_REQUESTED ? role -> true : 
requestedRoles::contains;
 
-    boolean allRoles = 
credentials.getPrincipalRoles().contains(PRINCIPAL_ROLE_ALL);
+    Set<String> activeRoles =
+        loadGrantsResult.getGrantRecords().stream()
+            .map(
+                gr ->
+                    metaStoreManager.loadEntity(
+                        callContext.getPolarisCallContext(),
+                        gr.getSecurableCatalogId(),
+                        gr.getSecurableId(),
+                        PolarisEntityType.PRINCIPAL_ROLE))
+            .filter(EntityResult::isSuccess)
+            .map(EntityResult::getEntity)
+            .map(PrincipalRoleEntity::of)
+            .map(PrincipalRoleEntity::getName)
+            .filter(includeRoleFilter)
+            .collect(Collectors.toSet());
 
-    Set<String> activatedPrincipalRoles = new HashSet<>();
-    if (!allRoles) {
-      activatedPrincipalRoles.addAll(
-          credentials.getPrincipalRoles().stream()
-              .filter(s -> s.startsWith(PRINCIPAL_ROLE_PREFIX))
-              .map(s -> s.substring(PRINCIPAL_ROLE_PREFIX.length()))
-              .toList());
+    if (requestedRoles != ALL_ROLES_REQUESTED && 
!activeRoles.containsAll(requestedRoles)) {
+      LOGGER
+          .atWarn()
+          .addKeyValue("principal", principal.getName())
+          .addKeyValue("credentials", credentials)
+          .addKeyValue("roles", activeRoles)
+          .log("Some principal roles were not found in the principal's 
grants");
     }
 
-    LOGGER.debug("Resolved principal: {}", principal);
+    return activeRoles;
+  }
 
-    return PersistedPolarisPrincipal.of(new PrincipalEntity(principal), 
activatedPrincipalRoles);
+  /**
+   * Extracts the requested roles from the credentials.
+   *
+   * <p>If the credentials contain the pseudo-role {@link 
#PRINCIPAL_ROLE_ALL}, it indicates that
+   * the principal is requesting all roles they have been granted in the 
system.
+   *
+   * <p>Otherwise, it filters the roles that start with the {@link 
#PRINCIPAL_ROLE_PREFIX} and
+   * returns the set of roles without the prefix.
+   */
+  protected Set<String> extractRequestedRoles(PolarisCredential credentials) {
+    Set<String> credentialsRoles = credentials.getPrincipalRoles();
+    if (credentialsRoles.contains(PRINCIPAL_ROLE_ALL)) {
+      return ALL_ROLES_REQUESTED;
+    }
+    if (credentialsRoles.stream().anyMatch(s -> 
!s.startsWith(PRINCIPAL_ROLE_PREFIX))) {
+      LOGGER
+          .atWarn()
+          .addKeyValue("credentials", credentials)
+          .addKeyValue("roles", credentialsRoles)
+          .log(
+              "Credentials contain roles that do not start with expected 
prefix '{}'. "
+                  + "These roles will be ignored during authentication.",
+              PRINCIPAL_ROLE_PREFIX);
+    }
+    return credentialsRoles.stream()
+        .filter(s -> s.startsWith(PRINCIPAL_ROLE_PREFIX))
+        .map(s -> s.substring(PRINCIPAL_ROLE_PREFIX.length()))
+        .collect(Collectors.toSet());
+  }
+
+  /**
+   * Loads the grants for the given principal.
+   *
+   * <p>This method retrieves the grants that the principal has been granted 
in the system, which
+   * will be used to determine the active roles for the principal.
+   */
+  protected LoadGrantsResult loadPrincipalGrants(PrincipalEntity principal) {
+    PolarisCallContext polarisContext = callContext.getPolarisCallContext();
+    LoadGrantsResult principalGrantResults =
+        metaStoreManager.loadGrantsToGrantee(polarisContext, principal);
+    diagnostics.check(
+        principalGrantResults.isSuccess(),
+        "Failed to resolve principal roles for principal name={} id={}",
+        principal.getName(),
+        principal.getId());
+    if (!principalGrantResults.isSuccess()) {
+      LOGGER.warn(
+          "Failed to resolve principal roles for principal name={} id={}",
+          principal.getName(),
+          principal.getId());
+      throw new NotAuthorizedException("Unable to authenticate");
+    }
+    return principalGrantResults;
   }
 }
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/PersistedPolarisPrincipal.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/auth/PersistedPolarisPrincipal.java
deleted file mode 100644
index c9b66c03c..000000000
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/auth/PersistedPolarisPrincipal.java
+++ /dev/null
@@ -1,60 +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.service.auth;
-
-import java.util.Map;
-import java.util.Set;
-import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.apache.polaris.core.entity.PrincipalEntity;
-import org.apache.polaris.immutables.PolarisImmutable;
-import org.immutables.value.Value;
-
-/**
- * A persisted {@link PolarisPrincipal}, exposing the underlying {@link 
PrincipalEntity}.
- *
- * <p>Note: This class is intended for internal use within the Polaris 
authentication system.
- */
-@PolarisImmutable
-abstract class PersistedPolarisPrincipal implements PolarisPrincipal {
-
-  /**
-   * Creates a new instance of {@link PersistedPolarisPrincipal} from the 
given {@link
-   * PrincipalEntity} and roles.
-   */
-  static PersistedPolarisPrincipal of(PrincipalEntity principalEntity, 
Set<String> principalRoles) {
-    return ImmutablePersistedPolarisPrincipal.builder()
-        .entity(principalEntity)
-        .roles(principalRoles)
-        .build();
-  }
-
-  abstract PrincipalEntity getEntity();
-
-  @Value.Derived
-  @Override
-  public String getName() {
-    return getEntity().getName();
-  }
-
-  @Value.Lazy
-  @Override
-  public Map<String, String> getProperties() {
-    return getEntity().getInternalPropertiesAsMap();
-  }
-}
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
index ff822e293..c833686b4 100644
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
+++ 
b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java
@@ -56,7 +56,6 @@ import org.apache.polaris.core.secrets.UserSecretsManager;
 import org.apache.polaris.core.secrets.UserSecretsManagerFactory;
 import org.apache.polaris.core.storage.cache.StorageCredentialCache;
 import org.apache.polaris.core.storage.cache.StorageCredentialCacheConfig;
-import org.apache.polaris.service.auth.ActiveRolesProvider;
 import org.apache.polaris.service.auth.AuthenticationConfiguration;
 import org.apache.polaris.service.auth.AuthenticationRealmConfiguration;
 import org.apache.polaris.service.auth.AuthenticationType;
@@ -385,16 +384,6 @@ public class ServiceProducers {
     return config.forRealm(realmContext);
   }
 
-  @Produces
-  @Deprecated
-  public ActiveRolesProvider activeRolesProvider(
-      AuthenticationRealmConfiguration config,
-      @Any Instance<ActiveRolesProvider> activeRolesProviders) {
-    return activeRolesProviders
-        .select(Identifier.Literal.of(config.activeRolesProvider().type()))
-        .get();
-  }
-
   @Produces
   public OidcTenantResolver oidcTenantResolver(
       org.apache.polaris.service.auth.external.OidcConfiguration config,
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/auth/ActiveRolesAugmentorTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/auth/ActiveRolesAugmentorTest.java
deleted file mode 100644
index 8d6e2d6bf..000000000
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/auth/ActiveRolesAugmentorTest.java
+++ /dev/null
@@ -1,117 +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.service.auth;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import io.quarkus.security.AuthenticationFailedException;
-import io.quarkus.security.identity.SecurityIdentity;
-import io.quarkus.security.runtime.QuarkusSecurityIdentity;
-import io.smallrye.mutiny.Uni;
-import java.security.Principal;
-import java.util.Set;
-import org.apache.polaris.core.auth.PolarisPrincipal;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
-@SuppressWarnings("deprecation")
-public class ActiveRolesAugmentorTest {
-
-  private ActiveRolesAugmentor augmentor;
-  private ActiveRolesProvider activeRolesProvider;
-
-  @BeforeEach
-  public void setup() {
-    activeRolesProvider = mock(ActiveRolesProvider.class);
-    augmentor = new ActiveRolesAugmentor(activeRolesProvider);
-  }
-
-  @Test
-  public void testAugmentAnonymousIdentity() {
-    // Given
-    SecurityIdentity anonymousIdentity =
-        QuarkusSecurityIdentity.builder().setAnonymous(true).build();
-
-    // When
-    Uni<SecurityIdentity> result = augmentor.augment(anonymousIdentity, null);
-
-    // Then
-    assertThat(result.await().indefinitely()).isSameAs(anonymousIdentity);
-  }
-
-  @Test
-  public void testAugmentNonPolarisPrincipal() {
-    // Given
-    Principal nonPolarisPrincipal = mock(Principal.class);
-    SecurityIdentity identity =
-        
QuarkusSecurityIdentity.builder().setPrincipal(nonPolarisPrincipal).build();
-
-    // When/Then
-    assertThatThrownBy(
-            () -> augmentor.augment(identity, 
Uni.createFrom()::item).await().indefinitely())
-        .isInstanceOf(AuthenticationFailedException.class)
-        .hasMessage("No Polaris principal found");
-  }
-
-  @ParameterizedTest
-  @ValueSource(strings = {"role1", "role1,role2", "role1,role2,role3"})
-  public void testAugmentWithValidRoles(String rolesString) {
-    // Given
-    Set<String> roles = Set.of(rolesString.split(","));
-    PolarisPrincipal polarisPrincipal = mock(PolarisPrincipal.class);
-    SecurityIdentity identity =
-        
QuarkusSecurityIdentity.builder().setPrincipal(polarisPrincipal).build();
-
-    
when(activeRolesProvider.getActiveRoles(polarisPrincipal)).thenReturn(roles);
-
-    // When
-    SecurityIdentity result =
-        augmentor.augment(identity, 
Uni.createFrom()::item).await().indefinitely();
-
-    // Then
-    assertThat(result).isNotNull();
-    assertThat(result.getPrincipal()).isSameAs(polarisPrincipal);
-    assertThat(result.getRoles()).containsExactlyInAnyOrderElementsOf(roles);
-  }
-
-  @Test
-  public void testAugmentWithEmptyRoles() {
-    // Given
-    Set<String> roles = Set.of();
-    PolarisPrincipal polarisPrincipal = mock(PolarisPrincipal.class);
-    SecurityIdentity identity =
-        
QuarkusSecurityIdentity.builder().setPrincipal(polarisPrincipal).build();
-
-    
when(activeRolesProvider.getActiveRoles(polarisPrincipal)).thenReturn(roles);
-
-    // When
-    SecurityIdentity result =
-        augmentor.augment(identity, 
Uni.createFrom()::item).await().indefinitely();
-
-    // Then
-    assertThat(result).isNotNull();
-    assertThat(result.getPrincipal()).isSameAs(polarisPrincipal);
-    assertThat(result.getRoles()).isEmpty();
-  }
-}
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
index e121fb75b..5924745d9 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultAuthenticatorTest.java
@@ -16,71 +16,291 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.polaris.service.auth;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.when;
 
+import io.quarkus.test.junit.QuarkusTest;
+import io.quarkus.test.junit.TestProfile;
+import io.smallrye.common.annotation.Identifier;
+import jakarta.inject.Inject;
+import java.time.Instant;
+import java.util.Set;
 import org.apache.iceberg.exceptions.NotAuthorizedException;
-import org.apache.iceberg.exceptions.ServiceFailureException;
-import org.apache.polaris.core.PolarisCallContext;
-import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.admin.model.PrincipalWithCredentials;
+import org.apache.polaris.core.auth.PolarisPrincipal;
+import org.apache.polaris.core.entity.PolarisEntityConstants;
 import org.apache.polaris.core.entity.PolarisEntityType;
-import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
-import org.apache.polaris.core.persistence.dao.entity.BaseResult;
-import org.apache.polaris.core.persistence.dao.entity.EntityResult;
-import org.assertj.core.api.Assertions;
+import org.apache.polaris.core.entity.PrincipalEntity;
+import org.apache.polaris.service.admin.PolarisAuthzTestBase;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
 import org.mockito.Mockito;
 
-public class DefaultAuthenticatorTest {
+@QuarkusTest
+@TestProfile(PolarisAuthzTestBase.Profile.class)
+public class DefaultAuthenticatorTest extends PolarisAuthzTestBase {
+
+  private static final String PRINCIPAL_NO_ROLES = "principal-no-roles";
+
+  @Inject
+  @Identifier("default")
+  DefaultAuthenticator authenticator;
 
-  private DefaultAuthenticator authenticator;
-  private PolarisMetaStoreManager metaStoreManager;
-  private PolarisCallContext polarisCallContext;
+  private PrincipalEntity principalNoRoles;
 
   @BeforeEach
-  public void setUp() {
-    RealmContext realmContext = () -> "test";
-    polarisCallContext = Mockito.mock(PolarisCallContext.class);
-    when(polarisCallContext.getRealmContext()).thenReturn(realmContext);
-    metaStoreManager = Mockito.mock(PolarisMetaStoreManager.class);
-    authenticator = new DefaultAuthenticator();
-    authenticator.metaStoreManager = metaStoreManager;
-    authenticator.callContext = polarisCallContext;
+  @Override
+  public void before(TestInfo testInfo) {
+    super.before(testInfo);
+
+    PrincipalWithCredentials principal =
+        adminService.createPrincipal(
+            new PrincipalEntity.Builder()
+                .setName(PRINCIPAL_NO_ROLES)
+                .setCreateTimestamp(Instant.now().toEpochMilli())
+                .build());
+
+    principalNoRoles =
+        rotateAndRefreshPrincipal(
+            metaStoreManager, PRINCIPAL_NO_ROLES, principal.getCredentials(), 
polarisContext);
+  }
+
+  @Test
+  void testNullPrincipalIdAndName() {
+    // Given: credentials with both null principal ID and name
+    PolarisCredential credentials =
+        PolarisCredential.of(null, null, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When/Then: authentication should fail with NotAuthorizedException
+    assertUnauthorized(credentials);
+  }
+
+  @Test
+  void testPrincipalNotFoundByName() {
+    // Given: credentials with a non-existent principal name
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null, "non-existent-principal", 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When/Then: authentication should fail with NotAuthorizedException
+    assertUnauthorized(credentials);
+  }
+
+  @Test
+  void testPrincipalNotFoundById() {
+    // Given: credentials with a non-existent principal ID
+    PolarisCredential credentials =
+        PolarisCredential.of(999999L, null, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When/Then: authentication should fail with NotAuthorizedException
+    assertUnauthorized(credentials);
   }
 
   @Test
   public void testFetchPrincipalThrowsServiceExceptionOnMetastoreException() {
-    PolarisCredential token = Mockito.mock(PolarisCredential.class);
-    long principalId = 100L;
-    when(token.getPrincipalId()).thenReturn(principalId);
-    when(metaStoreManager.loadEntity(
-            authenticator.callContext.getPolarisCallContext(),
-            0L,
-            principalId,
-            PolarisEntityType.PRINCIPAL))
+
+    // Given: credentials with a non-existent principal ID
+    PolarisCredential credentials =
+        PolarisCredential.of(123L, null, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    metaStoreManager = Mockito.spy(metaStoreManager);
+    when(metaStoreManager.loadEntity(polarisContext, 0L, 123L, 
PolarisEntityType.PRINCIPAL))
         .thenThrow(new RuntimeException("Metastore exception"));
 
-    Assertions.assertThatThrownBy(() -> authenticator.authenticate(token))
-        .isInstanceOf(ServiceFailureException.class)
-        .hasMessage("Unable to fetch principal entity");
+    assertUnauthorized(credentials);
   }
 
   @Test
-  public void testFetchPrincipalThrowsNotAuthorizedWhenNotFound() {
-    PolarisCredential token = Mockito.mock(PolarisCredential.class);
-    long principalId = 100L;
-    when(token.getPrincipalId()).thenReturn(principalId);
-    when(metaStoreManager.loadEntity(
-            authenticator.callContext.getPolarisCallContext(),
-            0L,
-            principalId,
-            PolarisEntityType.PRINCIPAL))
-        .thenReturn(new EntityResult(BaseResult.ReturnStatus.ENTITY_NOT_FOUND, 
""));
-
-    Assertions.assertThatThrownBy(() -> authenticator.authenticate(token))
+  void testAuthenticationByPrincipalId() {
+    // Given: credentials with principal ID instead of name
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            principalEntity.getId(), null, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with all assigned roles
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1, PRINCIPAL_ROLE2);
+  }
+
+  @Test
+  void testPrincipalFoundByName() {
+    // Given: credentials with existing principal name
+    PolarisCredential credentials =
+        PolarisCredential.of(null, PRINCIPAL_NAME, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with all assigned roles
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1, PRINCIPAL_ROLE2);
+  }
+
+  @Test
+  void testPrincipalFoundWithAllRolesRequested() {
+    // Given: credentials requesting all roles for an existing principal
+    PolarisCredential credentials =
+        PolarisCredential.of(null, PRINCIPAL_NAME, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with all assigned roles
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1, PRINCIPAL_ROLE2);
+  }
+
+  @Test
+  void testPrincipalFoundWithSubsetOfRolesRequested() {
+    // Given: credentials requesting only a subset of the principal's roles
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null,
+            PRINCIPAL_NAME,
+            Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX + 
PRINCIPAL_ROLE1));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with only the requested role
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1);
+  }
+
+  @Test
+  void testPrincipalFoundWithMultipleSpecificRolesRequested() {
+    // Given: credentials requesting multiple specific roles
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null,
+            PRINCIPAL_NAME,
+            Set.of(
+                DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX + PRINCIPAL_ROLE1,
+                DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX + PRINCIPAL_ROLE2));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with both requested roles
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1, PRINCIPAL_ROLE2);
+  }
+
+  @Test
+  void testPrincipalFoundButHasNoRolesAssigned() {
+    // Given: credentials for a principal with no assigned roles
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null, PRINCIPAL_NO_ROLES, 
Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with empty roles set
+    assertPrincipal(result, principalNoRoles);
+  }
+
+  @Test
+  void testRequestedRolesDoNotMapToSystemRoles() {
+    // Given: credentials requesting roles that don't exist in the system
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null,
+            PRINCIPAL_NAME,
+            Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX + 
"non-existent-role"));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with empty roles set (non-existent roles 
are filtered out)
+    assertPrincipal(result, principalEntity);
+  }
+
+  @Test
+  void testMixedValidAndInvalidRolesRequested() {
+    // Given: credentials requesting both valid and invalid roles
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null,
+            PRINCIPAL_NAME,
+            Set.of(
+                DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX + PRINCIPAL_ROLE1,
+                DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX
+                    + "non-existent-role" // This should be ignored
+                ));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with only the valid role
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1);
+  }
+
+  @Test
+  void testRolesWithoutPrefixAreIgnored() {
+    // Given: credentials with roles that don't have the required prefix
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null,
+            PRINCIPAL_NAME,
+            Set.of(
+                DefaultAuthenticator.PRINCIPAL_ROLE_PREFIX + PRINCIPAL_ROLE1,
+                "unprefixed-role", // This should be ignored
+                "another-unprefixed-role" // This should also be ignored
+                ));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with only the properly prefixed role
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1);
+  }
+
+  @Test
+  void testEmptyRolesRequestedReturnsEmptyRoles() {
+    // Given: credentials with empty roles set
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            null, PRINCIPAL_NAME, Set.of() // Empty roles set
+            );
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal with empty roles set
+    assertPrincipal(result, principalEntity);
+  }
+
+  @Test
+  void testPrincipalIdTakesPrecedenceOverName() {
+    // Given: credentials with both principal ID and name (ID should take 
precedence)
+    PolarisCredential credentials =
+        PolarisCredential.of(
+            principalEntity.getId(),
+            "wrong-name", // This should be ignored since ID is provided
+            Set.of(DefaultAuthenticator.PRINCIPAL_ROLE_ALL));
+
+    // When: authenticating the principal
+    PolarisPrincipal result = authenticator.authenticate(credentials);
+
+    // Then: should return principal resolved by ID, not name
+    assertPrincipal(result, principalEntity, PRINCIPAL_ROLE1, PRINCIPAL_ROLE2);
+  }
+
+  private void assertPrincipal(PolarisPrincipal result, PrincipalEntity 
entity, String... roles) {
+    assertThat(result).isNotNull();
+    assertThat(result.getName()).isEqualTo(entity.getName());
+    assertThat(result.getRoles()).containsExactlyInAnyOrder(roles);
+    assertThat(result.getProperties())
+        .containsKey(PolarisEntityConstants.getClientIdPropertyName());
+  }
+
+  private void assertUnauthorized(PolarisCredential credentials) {
+    assertThatThrownBy(() -> authenticator.authenticate(credentials))
         .isInstanceOf(NotAuthorizedException.class)
-        .hasMessage("Unable to authenticate");
+        .hasMessageContaining("Unable to authenticate");
   }
 }
diff --git a/site/content/in-dev/unreleased/external-idp.md 
b/site/content/in-dev/unreleased/external-idp.md
index 4e79a7199..01b465c64 100644
--- a/site/content/in-dev/unreleased/external-idp.md
+++ b/site/content/in-dev/unreleased/external-idp.md
@@ -59,16 +59,6 @@ polaris.authentication.authenticator.type=default
 polaris.authentication.realm1.authenticator.type=custom
 ```
 
-### Active Roles Provider
-
-The 
[`ActiveRolesProvider`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java)
 is a component responsible for determining which roles the principal is 
requesting and should be activated. It is common to all authentication types. 
-
-Only the `type` property is defined; it is used to define the provider 
implementation. It is overridable per realm:  
-
-```properties
-polaris.authentication.active-roles-provider.type=default
-```
-
 ## Internal Authentication Configuration 
 
 ### Token Broker
@@ -189,7 +179,7 @@ 
polaris.oidc.oidc-tenant1.principal-roles-mapper.mappings[0].regex=PRINCIPAL_ROL
 
polaris.oidc.oidc-tenant1.principal-roles-mapper.mappings[0].replacement=PRINCIPAL_ROLE:$1
 ```
 
-The default `ActiveRolesProvider` expects the security identity to expose role 
names in the following format: `PRINCIPAL_ROLE:<role name>`. You can use the 
`filter` and `mappings` properties to adjust the role names as they appear in 
the JWT claims. 
+The default `Authenticator` expects the security identity to expose role names 
in the following format: `PRINCIPAL_ROLE:<role name>`. You can use the `filter` 
and `mappings` properties to adjust the role names as they appear in the JWT 
claims. 
 
 For example, assume that the security identity produced by Quarkus exposes the 
following roles: `role_service_admin` and `role_catalog_admin`. Polaris expects 
`PRINCIPAL_ROLE:service_admin` and `PRINCIPAL_ROLE:catalog_admin` respectively. 
The following configuration can be used to achieve the desired mapping: 
 
@@ -217,11 +207,10 @@ Polaris separates authentication into two logical phases 
using [Quarkus Security
 
 ### Key Interfaces 
 
-- 
[`Authenticator`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/Authenticator.java):
 A core interface used to authenticate credentials. 
+- 
[`Authenticator`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/Authenticator.java):
 A core interface used to authenticate credentials and resolve principal and 
principal roles. Roles may be derived from OIDC claims or internal mappings. 
 - 
[`DecodedToken`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/DecodedToken.java):
 Used in internal auth and inherits from `PrincipalCredential`. 
-- 
[`ActiveRolesProvider`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java):
 Resolves the set of roles associated with the authenticated user for the 
current request. Roles may be derived from OIDC claims or internal mappings. 
 
-The 
[`DefaultAuthenticator`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java)
 is used to implement realm-specific logic based on these abstractions. 
+- The 
[`DefaultAuthenticator`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java)
 is used to implement realm-specific logic based on these abstractions. 
 
 ### Token Broker Configuration 
 
@@ -234,16 +223,14 @@ When internal authentication is enabled, Polaris uses 
token brokers to handle th
 1. 
[`InternalAuthenticationMechanism`](https://github.com/apache/polaris/blob/main/runtime/service/src/main/java/org/apache/polaris/service/quarkus/auth/internal/InternalAuthenticationMechanism.java)
 parses the auth header. 
 2. Uses 
[`TokenBroker`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/TokenBroker.java)
 to decode the token. 
 3. Builds 
[`PrincipalAuthInfo`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/PrincipalAuthInfo.java)
 and generates `SecurityIdentity` (Quarkus). 
-4. `Authenticator.authenticate()` validates the credential. 
-5. 
[`ActiveRolesProvider`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java)
 assigns roles. 
+4. `Authenticator.authenticate()` validates the credential, resolves the 
principal and principal roles, then creates the `PolarisPrincipal`. 
 
 ### External Authentication 
 
 1. `OidcAuthenticationMechanism` (Quarkus) processes the auth header. 
 2. 
[`OidcTenantResolvingAugmentor`](https://github.com/apache/polaris/blob/main/runtime/service/src/main/java/org/apache/polaris/service/quarkus/auth/external/OidcTenantResolvingAugmentor.java)
 selects the OIDC tenant. 
 3. 
[`PrincipalAuthInfoAugmentor`](https://github.com/apache/polaris/blob/main/runtime/service/src/main/java/org/apache/polaris/service/quarkus/auth/external/PrincipalAuthInfoAugmentor.java)
 extracts JWT claims. 
-4. `Authenticator.authenticate()` validates the claims. 
-5. 
[`ActiveRolesProvider`](https://github.com/apache/polaris/blob/main/service/common/src/main/java/org/apache/polaris/service/auth/ActiveRolesProvider.java)
 assigns roles. 
+4. `Authenticator.authenticate()` validates the claims, resolves the principal 
and principal roles, then creates the `PolarisPrincipal`.
 
 ### Mixed Authentication 
 

Reply via email to