Skip to content

Commit f2947ed

Browse files
authored
JWTBroker: fix refresh token logic (#1242)
1 parent fa409c9 commit f2947ed

File tree

2 files changed

+107
-3
lines changed

2 files changed

+107
-3
lines changed

quarkus/service/src/test/java/org/apache/polaris/service/quarkus/it/QuarkusApplicationIntegrationTest.java

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,26 @@
1919
package org.apache.polaris.service.quarkus.it;
2020

2121
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2223

2324
import com.auth0.jwt.JWT;
2425
import com.auth0.jwt.algorithms.Algorithm;
2526
import io.quarkus.test.junit.QuarkusTest;
2627
import io.quarkus.test.junit.QuarkusTestProfile;
2728
import io.quarkus.test.junit.TestProfile;
29+
import io.smallrye.common.annotation.Identifier;
30+
import jakarta.inject.Inject;
2831
import java.io.IOException;
2932
import java.time.Instant;
3033
import java.util.Map;
34+
import org.apache.iceberg.exceptions.NotAuthorizedException;
35+
import org.apache.iceberg.rest.ErrorHandlers;
3136
import org.apache.iceberg.rest.HTTPClient;
3237
import org.apache.iceberg.rest.RESTClient;
3338
import org.apache.iceberg.rest.auth.AuthConfig;
3439
import org.apache.iceberg.rest.auth.OAuth2Util;
40+
import org.apache.iceberg.rest.responses.OAuthTokenResponse;
41+
import org.apache.polaris.service.auth.TokenBrokerFactory;
3542
import org.apache.polaris.service.it.env.ClientCredentials;
3643
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
3744
import org.apache.polaris.service.it.test.PolarisApplicationIntegrationTest;
@@ -53,8 +60,12 @@ public Map<String, String> getConfigOverrides() {
5360
}
5461
}
5562

63+
@Inject
64+
@Identifier("rsa-key-pair")
65+
TokenBrokerFactory tokenBrokerFactory;
66+
5667
@Test
57-
public void testIcebergRestApiRefreshToken(
68+
public void testIcebergRestApiRefreshExpiredToken(
5869
PolarisApiEndpoints endpoints, ClientCredentials clientCredentials) throws IOException {
5970
String path = endpoints.catalogApiEndpoint() + "/v1/oauth/tokens";
6071
try (RESTClient client =
@@ -82,4 +93,91 @@ public void testIcebergRestApiRefreshToken(
8293
assertThat(JWT.decode(session.token()).getExpiresAtAsInstant()).isAfter(Instant.EPOCH);
8394
}
8495
}
96+
97+
@Test
98+
public void testIcebergRestApiRefreshValidToken(
99+
PolarisApiEndpoints endpoints, ClientCredentials clientCredentials) throws IOException {
100+
String path = endpoints.catalogApiEndpoint() + "/v1/oauth/tokens";
101+
try (RESTClient client =
102+
HTTPClient.builder(Map.of())
103+
.withHeader(endpoints.realmHeaderName(), endpoints.realmId())
104+
.uri(path)
105+
.build()) {
106+
var response =
107+
client.postForm(
108+
path,
109+
Map.of(
110+
"grant_type",
111+
"client_credentials",
112+
"scope",
113+
"PRINCIPAL_ROLE:ALL",
114+
"client_id",
115+
clientCredentials.clientId(),
116+
"client_secret",
117+
clientCredentials.clientSecret()),
118+
OAuthTokenResponse.class,
119+
Map.of(),
120+
ErrorHandlers.oauthErrorHandler());
121+
String token = response.token();
122+
var authConfig =
123+
AuthConfig.builder()
124+
.credential(clientCredentials.clientId() + ":" + clientCredentials.clientSecret())
125+
.scope("PRINCIPAL_ROLE:ALL")
126+
.oauth2ServerUri(path)
127+
.token(token)
128+
.build();
129+
var parentSession = new OAuth2Util.AuthSession(Map.of(), authConfig);
130+
var session = OAuth2Util.AuthSession.fromAccessToken(client, null, token, 0L, parentSession);
131+
session.refresh(client);
132+
assertThat(session.token()).isNotEqualTo(token);
133+
assertThat(JWT.decode(session.token()).getExpiresAtAsInstant()).isAfter(Instant.now());
134+
}
135+
}
136+
137+
@Test
138+
public void testIcebergRestApiInvalidToken(
139+
PolarisApiEndpoints endpoints, ClientCredentials clientCredentials) throws IOException {
140+
String path = endpoints.catalogApiEndpoint() + "/v1/oauth/tokens";
141+
try (RESTClient client =
142+
HTTPClient.builder(Map.of())
143+
.withHeader(endpoints.realmHeaderName(), endpoints.realmId())
144+
.uri(path)
145+
.build()) {
146+
var response =
147+
client.postForm(
148+
path,
149+
Map.of(
150+
"grant_type",
151+
"client_credentials",
152+
"scope",
153+
"PRINCIPAL_ROLE:ALL",
154+
"client_id",
155+
clientCredentials.clientId(),
156+
"client_secret",
157+
clientCredentials.clientSecret()),
158+
OAuthTokenResponse.class,
159+
Map.of(),
160+
ErrorHandlers.oauthErrorHandler());
161+
String token = response.token();
162+
// mimics OAUth2Util.AuthSession refreshing the token
163+
assertThatThrownBy(
164+
() ->
165+
client.postForm(
166+
path,
167+
Map.of(
168+
"grant_type",
169+
"urn:ietf:params:oauth:grant-type:token-exchange",
170+
"scope",
171+
"PRINCIPAL_ROLE:ALL",
172+
"subject_token",
173+
"invalid",
174+
"subject_token_type",
175+
"urn:ietf:params:oauth:token-type:access_token"),
176+
OAuthTokenResponse.class,
177+
Map.of("Authorization", "Bearer " + token),
178+
ErrorHandlers.oauthErrorHandler()))
179+
.isInstanceOf(NotAuthorizedException.class)
180+
.hasMessageContaining("invalid_client");
181+
}
182+
}
85183
}

service/common/src/main/java/org/apache/polaris/service/auth/JWTBroker.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.apache.polaris.core.entity.PrincipalEntity;
3636
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
3737
import org.apache.polaris.core.persistence.dao.entity.EntityResult;
38+
import org.apache.polaris.service.auth.OAuthTokenErrorResponse.Error;
3839
import org.apache.polaris.service.types.TokenType;
3940
import org.slf4j.Logger;
4041
import org.slf4j.LoggerFactory;
@@ -100,7 +101,7 @@ public TokenResponse generateFromToken(
100101
String grantType,
101102
String scope,
102103
TokenType requestedTokenType) {
103-
if (!TokenType.ACCESS_TOKEN.equals(requestedTokenType)) {
104+
if (requestedTokenType != null && !TokenType.ACCESS_TOKEN.equals(requestedTokenType)) {
104105
return new TokenResponse(OAuthTokenErrorResponse.Error.invalid_request);
105106
}
106107
if (!TokenType.ACCESS_TOKEN.equals(subjectTokenType)) {
@@ -109,7 +110,12 @@ public TokenResponse generateFromToken(
109110
if (StringUtils.isBlank(subjectToken)) {
110111
return new TokenResponse(OAuthTokenErrorResponse.Error.invalid_request);
111112
}
112-
DecodedToken decodedToken = verify(subjectToken);
113+
DecodedToken decodedToken;
114+
try {
115+
decodedToken = verify(subjectToken);
116+
} catch (NotAuthorizedException e) {
117+
return new TokenResponse(Error.invalid_client);
118+
}
113119
EntityResult principalLookup =
114120
metaStoreManager.loadEntity(
115121
CallContext.getCurrentContext().getPolarisCallContext(),

0 commit comments

Comments
 (0)