Skip to content

Commit 81bfe62

Browse files
authored
Added user token to the PolarisPrincipal (#3236)
* Added user token to the PolarisPrincipal * added redacted
1 parent 27ae684 commit 81bfe62

File tree

5 files changed

+96
-5
lines changed

5 files changed

+96
-5
lines changed

polaris-core/src/main/java/org/apache/polaris/core/auth/PolarisPrincipal.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020

2121
import java.security.Principal;
2222
import java.util.Map;
23+
import java.util.Optional;
2324
import java.util.Set;
2425
import org.apache.polaris.core.entity.PrincipalEntity;
2526
import org.apache.polaris.immutables.PolarisImmutable;
27+
import org.immutables.value.Value;
2628

2729
/** Represents a {@link Principal} in the Polaris system. */
2830
@PolarisImmutable
@@ -39,7 +41,28 @@ public interface PolarisPrincipal extends Principal {
3941
* @param roles the set of roles associated with the principal
4042
*/
4143
static PolarisPrincipal of(PrincipalEntity principalEntity, Set<String> roles) {
42-
return of(principalEntity.getName(), principalEntity.getInternalPropertiesAsMap(), roles);
44+
return of(
45+
principalEntity.getName(),
46+
principalEntity.getInternalPropertiesAsMap(),
47+
roles,
48+
Optional.empty());
49+
}
50+
51+
/**
52+
* Creates a new instance of {@link PolarisPrincipal} from the given {@link PrincipalEntity} and
53+
* roles.
54+
*
55+
* <p>The created principal will have the same ID and name as the {@link PrincipalEntity}, and its
56+
* properties will be derived from the internal properties of the entity.
57+
*
58+
* @param principalEntity the principal entity representing the user or service
59+
* @param roles the set of roles associated with the principal
60+
* @param token the access token of the current user
61+
*/
62+
static PolarisPrincipal of(
63+
PrincipalEntity principalEntity, Set<String> roles, Optional<String> token) {
64+
return of(
65+
principalEntity.getName(), principalEntity.getInternalPropertiesAsMap(), roles, token);
4366
}
4467

4568
/**
@@ -51,9 +74,24 @@ static PolarisPrincipal of(PrincipalEntity principalEntity, Set<String> roles) {
5174
* @param roles the set of roles associated with the principal
5275
*/
5376
static PolarisPrincipal of(String name, Map<String, String> properties, Set<String> roles) {
77+
return of(name, properties, roles, Optional.empty());
78+
}
79+
80+
/**
81+
* Creates a new instance of {@link PolarisPrincipal} with the specified ID, name, roles, and
82+
* properties.
83+
*
84+
* @param name the name of the principal
85+
* @param properties additional properties associated with the principal
86+
* @param roles the set of roles associated with the principal
87+
* @param token the access token of the current user
88+
*/
89+
static PolarisPrincipal of(
90+
String name, Map<String, String> properties, Set<String> roles, Optional<String> token) {
5491
return ImmutablePolarisPrincipal.builder()
5592
.name(name)
5693
.properties(properties)
94+
.token(token)
5795
.roles(roles)
5896
.build();
5997
}
@@ -74,4 +112,8 @@ static PolarisPrincipal of(String name, Map<String, String> properties, Set<Stri
74112
* as permissions, preferences, or other metadata.
75113
*/
76114
Map<String, String> getProperties();
115+
116+
/** Optionally returns the access token of the current user. */
117+
@Value.Redacted
118+
Optional<String> getToken();
77119
}

runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultAuthenticator.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.smallrye.common.annotation.Identifier;
2323
import jakarta.enterprise.context.RequestScoped;
2424
import jakarta.inject.Inject;
25+
import java.util.Optional;
2526
import java.util.Set;
2627
import java.util.function.Predicate;
2728
import java.util.stream.Collectors;
@@ -94,7 +95,9 @@ public PolarisPrincipal authenticate(PolarisCredential credentials) {
9495

9596
PrincipalEntity principalEntity = resolvePrincipalEntity(credentials);
9697
Set<String> principalRoles = resolvePrincipalRoles(credentials, principalEntity);
97-
PolarisPrincipal polarisPrincipal = PolarisPrincipal.of(principalEntity, principalRoles);
98+
PolarisPrincipal polarisPrincipal =
99+
PolarisPrincipal.of(
100+
principalEntity, principalRoles, Optional.ofNullable(credentials.getToken()));
98101

99102
LOGGER.debug("Resolved principal: {}", polarisPrincipal);
100103
return polarisPrincipal;

runtime/service/src/main/java/org/apache/polaris/service/auth/PolarisCredential.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,19 @@ public interface PolarisCredential extends Credential {
3232

3333
static PolarisCredential of(
3434
@Nullable Long principalId, @Nullable String principalName, Set<String> principalRoles) {
35+
return of(principalId, principalName, principalRoles, null);
36+
}
37+
38+
static PolarisCredential of(
39+
@Nullable Long principalId,
40+
@Nullable String principalName,
41+
Set<String> principalRoles,
42+
@Nullable String token) {
3543
return ImmutablePolarisCredential.builder()
3644
.principalId(principalId)
3745
.principalName(principalName)
3846
.principalRoles(principalRoles)
47+
.token(token)
3948
.build();
4049
}
4150

@@ -49,4 +58,8 @@ static PolarisCredential of(
4958

5059
/** The principal roles, or empty if the principal has no roles. */
5160
Set<String> getPrincipalRoles();
61+
62+
/** The access token of the current user, or null if not applicable. */
63+
@Nullable
64+
String getToken();
5265
}

runtime/service/src/main/java/org/apache/polaris/service/auth/external/OidcPolarisCredentialAugmentor.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ protected SecurityIdentity setPolarisCredential(
9090
principalMapper.mapPrincipalId(identity).stream().boxed().findFirst().orElse(null);
9191
String principalName = principalMapper.mapPrincipalName(identity).orElse(null);
9292
Set<String> principalRoles = rolesMapper.mapPrincipalRoles(identity);
93-
PolarisCredential credential = PolarisCredential.of(principalId, principalName, principalRoles);
93+
String token = null;
94+
if (identity.getPrincipal() instanceof JsonWebToken jwt) {
95+
token = jwt.getRawToken();
96+
}
97+
PolarisCredential credential =
98+
PolarisCredential.of(principalId, principalName, principalRoles, token);
9499
// Note: we don't change the identity roles here, this will be done later on
95100
// by the AuthenticatingAugmentor, which will also validate them.
96101
return QuarkusSecurityIdentity.builder(identity).addCredential(credential).build();

runtime/service/src/test/java/org/apache/polaris/service/auth/external/OidcPolarisCredentialAugmentorTest.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public void testAugmentNonOidcPrincipal() {
100100
}
101101

102102
@Test
103-
public void testAugmentOidcPrincipal() {
103+
public void testAugmentOidcPrincipalWithToken() {
104104
// Given
105105
JsonWebToken oidcPrincipal = mock(JsonWebToken.class);
106106
SecurityIdentity identity =
@@ -109,7 +109,35 @@ public void testAugmentOidcPrincipal() {
109109
.addRole("ROLE1")
110110
.addAttribute(TENANT_CONFIG_ATTRIBUTE, config)
111111
.build();
112+
when(oidcPrincipal.getRawToken()).thenReturn("this_is_a_token");
113+
when(principalMapper.mapPrincipalId(identity)).thenReturn(OptionalLong.of(123L));
114+
when(principalMapper.mapPrincipalName(identity)).thenReturn(Optional.of("root"));
115+
when(principalRolesMapper.mapPrincipalRoles(identity)).thenReturn(Set.of("MAPPED_ROLE1"));
116+
117+
// When
118+
SecurityIdentity result =
119+
augmentor.augment(identity, Uni.createFrom()::item).await().indefinitely();
120+
121+
// Then
122+
assertThat(result).isNotNull();
123+
assertThat(result.getPrincipal()).isSameAs(oidcPrincipal);
124+
assertThat(result.getCredential(PolarisCredential.class))
125+
.isEqualTo(PolarisCredential.of(123L, "root", Set.of("MAPPED_ROLE1"), "this_is_a_token"));
126+
// the identity roles should not change, since this is done by the ActiveRolesAugmentor
127+
assertThat(result.getRoles()).containsExactlyInAnyOrder("ROLE1");
128+
}
112129

130+
@Test
131+
public void testAugmentOidcPrincipalWithNoToken() {
132+
// Given
133+
JsonWebToken oidcPrincipal = mock(JsonWebToken.class);
134+
SecurityIdentity identity =
135+
QuarkusSecurityIdentity.builder()
136+
.setPrincipal(oidcPrincipal)
137+
.addRole("ROLE1")
138+
.addAttribute(TENANT_CONFIG_ATTRIBUTE, config)
139+
.build();
140+
when(oidcPrincipal.getRawToken()).thenReturn(null);
113141
when(principalMapper.mapPrincipalId(identity)).thenReturn(OptionalLong.of(123L));
114142
when(principalMapper.mapPrincipalName(identity)).thenReturn(Optional.of("root"));
115143
when(principalRolesMapper.mapPrincipalRoles(identity)).thenReturn(Set.of("MAPPED_ROLE1"));
@@ -122,7 +150,7 @@ public void testAugmentOidcPrincipal() {
122150
assertThat(result).isNotNull();
123151
assertThat(result.getPrincipal()).isSameAs(oidcPrincipal);
124152
assertThat(result.getCredential(PolarisCredential.class))
125-
.isEqualTo(PolarisCredential.of(123L, "root", Set.of("MAPPED_ROLE1")));
153+
.isEqualTo(PolarisCredential.of(123L, "root", Set.of("MAPPED_ROLE1"), null));
126154
// the identity roles should not change, since this is done by the ActiveRolesAugmentor
127155
assertThat(result.getRoles()).containsExactlyInAnyOrder("ROLE1");
128156
}

0 commit comments

Comments
 (0)