diff --git a/aws/src/main/java/org/apache/iceberg/aws/RESTSigV4Signer.java b/aws/src/main/java/org/apache/iceberg/aws/RESTSigV4Signer.java
index c1c858416352..fc9de32cc6fd 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/RESTSigV4Signer.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/RESTSigV4Signer.java
@@ -51,7 +51,10 @@
*
See Signing AWS
* API requests for details about the protocol.
+ *
+ * @deprecated since 1.9.0, will be removed in 1.10.0; use {@link RESTSigV4AuthManager} instead.
*/
+@Deprecated
public class RESTSigV4Signer implements HttpRequestInterceptor {
static final String EMPTY_BODY_SHA256 =
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
diff --git a/aws/src/main/java/org/apache/iceberg/aws/s3/VendedCredentialsProvider.java b/aws/src/main/java/org/apache/iceberg/aws/s3/VendedCredentialsProvider.java
index 769d187875db..da96f4cb8f48 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/s3/VendedCredentialsProvider.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/s3/VendedCredentialsProvider.java
@@ -28,11 +28,10 @@
import org.apache.iceberg.relocated.com.google.common.base.Strings;
import org.apache.iceberg.rest.ErrorHandlers;
import org.apache.iceberg.rest.HTTPClient;
-import org.apache.iceberg.rest.HTTPHeaders;
import org.apache.iceberg.rest.RESTClient;
-import org.apache.iceberg.rest.auth.DefaultAuthSession;
-import org.apache.iceberg.rest.auth.OAuth2Properties;
-import org.apache.iceberg.rest.auth.OAuth2Util;
+import org.apache.iceberg.rest.auth.AuthManager;
+import org.apache.iceberg.rest.auth.AuthManagers;
+import org.apache.iceberg.rest.auth.AuthSession;
import org.apache.iceberg.rest.credentials.Credential;
import org.apache.iceberg.rest.responses.LoadCredentialsResponse;
import software.amazon.awssdk.auth.credentials.AwsCredentials;
@@ -48,6 +47,8 @@ public class VendedCredentialsProvider implements AwsCredentialsProvider, SdkAut
private volatile HTTPClient client;
private final Map properties;
private final CachedSupplier credentialCache;
+ private AuthManager authManager;
+ private AuthSession authSession;
private VendedCredentialsProvider(Map properties) {
Preconditions.checkArgument(null != properties, "Invalid properties: null");
@@ -66,8 +67,10 @@ public AwsCredentials resolveCredentials() {
@Override
public void close() {
- IoUtils.closeQuietly(client, null);
- credentialCache.close();
+ IoUtils.closeQuietlyV2(authSession, null);
+ IoUtils.closeQuietlyV2(authManager, null);
+ IoUtils.closeQuietlyV2(client, null);
+ IoUtils.closeQuietlyV2(credentialCache, null);
}
public static VendedCredentialsProvider create(Map properties) {
@@ -78,14 +81,10 @@ private RESTClient httpClient() {
if (null == client) {
synchronized (this) {
if (null == client) {
- DefaultAuthSession authSession =
- DefaultAuthSession.of(
- HTTPHeaders.of(OAuth2Util.authHeaders(properties.get(OAuth2Properties.TOKEN))));
- client =
- HTTPClient.builder(properties)
- .uri(properties.get(URI))
- .withAuthSession(authSession)
- .build();
+ authManager = AuthManagers.loadAuthManager("s3-credentials-refresh", properties);
+ HTTPClient httpClient = HTTPClient.builder(properties).uri(properties.get(URI)).build();
+ authSession = authManager.catalogSession(httpClient, properties);
+ client = httpClient.withAuthSession(authSession);
}
}
}
diff --git a/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java b/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java
index 8f5a00b49daf..647291d394cd 100644
--- a/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java
+++ b/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java
@@ -20,15 +20,12 @@
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
-import com.github.benmanes.caffeine.cache.RemovalListener;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
-import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -42,13 +39,12 @@
import org.apache.iceberg.rest.HTTPClient;
import org.apache.iceberg.rest.RESTClient;
import org.apache.iceberg.rest.ResourcePaths;
-import org.apache.iceberg.rest.auth.AuthConfig;
+import org.apache.iceberg.rest.auth.AuthManager;
+import org.apache.iceberg.rest.auth.AuthManagers;
+import org.apache.iceberg.rest.auth.AuthSession;
import org.apache.iceberg.rest.auth.OAuth2Properties;
import org.apache.iceberg.rest.auth.OAuth2Util;
-import org.apache.iceberg.rest.auth.OAuth2Util.AuthSession;
-import org.apache.iceberg.rest.responses.OAuthTokenResponse;
import org.apache.iceberg.util.PropertyUtil;
-import org.apache.iceberg.util.ThreadPools;
import org.immutables.value.Value;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,7 +60,7 @@
@Value.Immutable
public abstract class S3V4RestSignerClient
- extends AbstractAws4Signer {
+ extends AbstractAws4Signer implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(S3V4RestSignerClient.class);
public static final String S3_SIGNER_URI = "s3.signer.uri";
@@ -81,13 +77,14 @@ public abstract class S3V4RestSignerClient
private static final String SCOPE = "sign";
@SuppressWarnings("immutables:incompat")
- private static volatile ScheduledExecutorService tokenRefreshExecutor;
+ private volatile AuthManager authManager;
- @SuppressWarnings("immutables:incompat")
- private static volatile RESTClient httpClient;
+ @SuppressWarnings({"immutables:incompat", "VisibilityModifier"})
+ @VisibleForTesting
+ static volatile RESTClient httpClient;
@SuppressWarnings("immutables:incompat")
- private static volatile Cache authSessionCache;
+ private volatile AuthSession authSession;
public abstract Map properties();
@@ -138,52 +135,6 @@ boolean keepTokenRefreshed() {
OAuth2Properties.TOKEN_REFRESH_ENABLED_DEFAULT);
}
- @VisibleForTesting
- ScheduledExecutorService tokenRefreshExecutor() {
- if (!keepTokenRefreshed()) {
- return null;
- }
-
- if (null == tokenRefreshExecutor) {
- synchronized (S3V4RestSignerClient.class) {
- if (null == tokenRefreshExecutor) {
- tokenRefreshExecutor = ThreadPools.newScheduledPool("s3-signer-token-refresh", 1);
- }
- }
- }
-
- return tokenRefreshExecutor;
- }
-
- private Cache authSessionCache() {
- if (null == authSessionCache) {
- synchronized (S3V4RestSignerClient.class) {
- if (null == authSessionCache) {
- long expirationIntervalMs =
- PropertyUtil.propertyAsLong(
- properties(),
- CatalogProperties.AUTH_SESSION_TIMEOUT_MS,
- CatalogProperties.AUTH_SESSION_TIMEOUT_MS_DEFAULT);
-
- authSessionCache =
- Caffeine.newBuilder()
- .expireAfterAccess(Duration.ofMillis(expirationIntervalMs))
- .removalListener(
- (RemovalListener)
- (id, auth, cause) -> {
- if (null != auth) {
- LOG.trace("Stopping refresh for AuthSession");
- auth.stopRefreshing();
- }
- })
- .build();
- }
- }
- }
-
- return authSessionCache;
- }
-
private RESTClient httpClient() {
if (null == httpClient) {
synchronized (S3V4RestSignerClient.class) {
@@ -200,86 +151,40 @@ private RESTClient httpClient() {
return httpClient;
}
- private AuthSession authSession() {
- String token = token().get();
- if (null != token) {
- return authSessionCache()
- .get(
- token,
- id -> {
- // this client will be reused for token refreshes; it must contain an empty auth
- // session in order to avoid interfering with refreshed tokens
- RESTClient refreshClient =
- httpClient().withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY);
- return AuthSession.fromAccessToken(
- refreshClient,
- tokenRefreshExecutor(),
- token,
- expiresAtMillis(properties()),
- new AuthSession(
- ImmutableMap.of(),
- AuthConfig.builder()
- .token(token)
- .credential(credential())
- .scope(SCOPE)
- .oauth2ServerUri(oauth2ServerUri())
- .optionalOAuthParams(optionalOAuthParams())
- .build()));
- });
- }
-
- if (credentialProvided()) {
- return authSessionCache()
- .get(
- credential(),
- id -> {
- AuthSession session =
- new AuthSession(
- ImmutableMap.of(),
- AuthConfig.builder()
- .credential(credential())
- .scope(SCOPE)
- .oauth2ServerUri(oauth2ServerUri())
- .optionalOAuthParams(optionalOAuthParams())
- .build());
- long startTimeMillis = System.currentTimeMillis();
- // this client will be reused for token refreshes; it must contain an empty auth
- // session in order to avoid interfering with refreshed tokens
- RESTClient refreshClient =
- httpClient().withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY);
- OAuthTokenResponse authResponse =
- OAuth2Util.fetchToken(
- refreshClient,
- session.headers(),
- credential(),
- SCOPE,
- oauth2ServerUri(),
- optionalOAuthParams());
- return AuthSession.fromTokenResponse(
- refreshClient, tokenRefreshExecutor(), authResponse, startTimeMillis, session);
- });
+ @VisibleForTesting
+ AuthSession authSession() {
+ if (null == authSession) {
+ synchronized (S3V4RestSignerClient.class) {
+ if (null == authSession) {
+ authManager = AuthManagers.loadAuthManager("s3-signer", properties());
+ ImmutableMap.Builder properties =
+ ImmutableMap.builder()
+ .putAll(properties())
+ .putAll(optionalOAuthParams())
+ .put(OAuth2Properties.OAUTH2_SERVER_URI, oauth2ServerUri())
+ .put(OAuth2Properties.TOKEN_REFRESH_ENABLED, String.valueOf(keepTokenRefreshed()))
+ .put(OAuth2Properties.SCOPE, SCOPE);
+ String token = token().get();
+ if (null != token) {
+ properties.put(OAuth2Properties.TOKEN, token);
+ }
+
+ if (credentialProvided()) {
+ properties.put(OAuth2Properties.CREDENTIAL, credential());
+ }
+
+ authSession = authManager.tableSession(httpClient(), properties.buildKeepingLast());
+ }
+ }
}
- return AuthSession.empty();
+ return authSession;
}
private boolean credentialProvided() {
return null != credential() && !credential().isEmpty();
}
- private Long expiresAtMillis(Map properties) {
- if (properties.containsKey(OAuth2Properties.TOKEN_EXPIRES_IN_MS)) {
- long expiresInMillis =
- PropertyUtil.propertyAsLong(
- properties,
- OAuth2Properties.TOKEN_EXPIRES_IN_MS,
- OAuth2Properties.TOKEN_EXPIRES_IN_MS_DEFAULT);
- return System.currentTimeMillis() + expiresInMillis;
- } else {
- return null;
- }
- }
-
@Value.Check
protected void check() {
Preconditions.checkArgument(
@@ -377,6 +282,12 @@ public SdkHttpFullRequest sign(
return mutableRequest.build();
}
+ @Override
+ public void close() throws Exception {
+ IoUtils.closeQuietlyV2(authSession, null);
+ IoUtils.closeQuietlyV2(authManager, null);
+ }
+
/**
* Only add body for DeleteObjectsRequest. Refer to
* https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html#API_DeleteObjects_RequestSyntax
diff --git a/aws/src/test/java/org/apache/iceberg/aws/TestRESTSigV4Signer.java b/aws/src/test/java/org/apache/iceberg/aws/TestRESTSigV4Signer.java
index cc8873e30ea7..462663711225 100644
--- a/aws/src/test/java/org/apache/iceberg/aws/TestRESTSigV4Signer.java
+++ b/aws/src/test/java/org/apache/iceberg/aws/TestRESTSigV4Signer.java
@@ -27,6 +27,10 @@
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
import org.apache.iceberg.rest.HTTPClient;
+import org.apache.iceberg.rest.RESTClient;
+import org.apache.iceberg.rest.auth.AuthManager;
+import org.apache.iceberg.rest.auth.AuthManagers;
+import org.apache.iceberg.rest.auth.AuthProperties;
import org.apache.iceberg.rest.auth.AuthSession;
import org.apache.iceberg.rest.auth.OAuth2Util;
import org.apache.iceberg.rest.responses.ConfigResponse;
@@ -39,12 +43,15 @@
import org.mockserver.model.Header;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;
+import org.mockserver.model.Parameter;
+import org.mockserver.model.ParameterBody;
import org.mockserver.verify.VerificationTimes;
import software.amazon.awssdk.auth.signer.internal.SignerConstant;
public class TestRESTSigV4Signer {
private static ClientAndServer mockServer;
- private static HTTPClient client;
+ private static RESTClient client;
+ private static AuthManager authManager;
@BeforeAll
public static void beforeClass() {
@@ -52,26 +59,35 @@ public static void beforeClass() {
Map properties =
ImmutableMap.of(
- "rest.sigv4-enabled",
- "true",
+ AuthProperties.AUTH_TYPE,
+ AuthProperties.AUTH_TYPE_SIGV4,
// CI environment doesn't have credentials, but a value must be set for signing
AwsProperties.REST_SIGNER_REGION,
"us-west-2",
AwsProperties.REST_ACCESS_KEY_ID,
"id",
AwsProperties.REST_SECRET_ACCESS_KEY,
- "secret");
- client =
+ "secret",
+ // OAuth2 token to test relocation of conflicting auth header
+ "token",
+ "existing_token");
+
+ HTTPClient httpClient =
HTTPClient.builder(properties)
.uri("http://localhost:" + mockServer.getLocalPort())
- .withHeader(HttpHeaders.AUTHORIZATION, "Bearer existing_token")
- .withAuthSession(AuthSession.EMPTY)
- .build();
+ .build()
+ .withAuthSession(AuthSession.EMPTY);
+
+ authManager = AuthManagers.loadAuthManager("test", properties);
+ AuthSession authSession = authManager.catalogSession(httpClient, properties);
+
+ client = httpClient.withAuthSession(authSession);
}
@AfterAll
public static void afterClass() throws IOException {
mockServer.stop();
+ authManager.close();
client.close();
}
@@ -90,11 +106,13 @@ public void signRequestWithoutBody() {
.withHeader(Header.header(HttpHeaders.AUTHORIZATION, "AWS4-HMAC-SHA256.*"))
// Require that conflicting auth header is relocated
.withHeader(
- Header.header(RESTSigV4Signer.RELOCATED_HEADER_PREFIX + HttpHeaders.AUTHORIZATION))
+ Header.header(
+ RESTSigV4AuthSession.RELOCATED_HEADER_PREFIX + HttpHeaders.AUTHORIZATION,
+ "Bearer existing_token"))
// Require the empty body checksum
.withHeader(
Header.header(
- SignerConstant.X_AMZ_CONTENT_SHA256, RESTSigV4Signer.EMPTY_BODY_SHA256));
+ SignerConstant.X_AMZ_CONTENT_SHA256, RESTSigV4AuthSession.EMPTY_BODY_SHA256));
mockServer
.when(request)
@@ -113,11 +131,18 @@ public void signRequestWithBody() {
HttpRequest.request()
.withMethod("POST")
.withPath("/v1/oauth/token")
+ .withBody(
+ ParameterBody.params(
+ Parameter.param("client_id", "asdfasd"),
+ Parameter.param("client_secret", "asdfasdf"),
+ Parameter.param("scope", "catalog")))
// Require SigV4 Authorization
.withHeader(Header.header(HttpHeaders.AUTHORIZATION, "AWS4-HMAC-SHA256.*"))
// Require that conflicting auth header is relocated
.withHeader(
- Header.header(RESTSigV4Signer.RELOCATED_HEADER_PREFIX + HttpHeaders.AUTHORIZATION))
+ Header.header(
+ RESTSigV4AuthSession.RELOCATED_HEADER_PREFIX + HttpHeaders.AUTHORIZATION,
+ "Bearer existing_token"))
// Require a body checksum is set
.withHeader(Header.header(SignerConstant.X_AMZ_CONTENT_SHA256));
diff --git a/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3RestSigner.java b/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3RestSigner.java
index 313214c4e98f..34f5f2c710c8 100644
--- a/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3RestSigner.java
+++ b/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3RestSigner.java
@@ -19,6 +19,7 @@
package org.apache.iceberg.aws.s3.signer;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.InstanceOfAssertFactories.type;
import java.net.URI;
import java.nio.file.Path;
@@ -112,11 +113,6 @@ public static void beforeClass() throws Exception {
@AfterAll
public static void afterClass() throws Exception {
- assertThat(validatingSigner.icebergSigner.tokenRefreshExecutor())
- .isInstanceOf(ScheduledThreadPoolExecutor.class);
-
- ScheduledThreadPoolExecutor executor =
- ((ScheduledThreadPoolExecutor) validatingSigner.icebergSigner.tokenRefreshExecutor());
// token expiration is set to 10000s by the S3SignerServlet so there should be exactly one token
// scheduled for refresh. Such a high token expiration value is explicitly selected to be much
// larger than TestS3RestSigner would need to execute all tests.
@@ -124,16 +120,23 @@ public static void afterClass() throws Exception {
// there aren't other token refreshes being scheduled after every sign request and after
// TestS3RestSigner completes all tests, there should be only this single token in the queue
// that is scheduled for refresh
- assertThat(executor.getPoolSize()).isEqualTo(1);
- assertThat(executor.getQueue())
- .as("should only have a single token scheduled for refresh")
- .hasSize(1);
- assertThat(executor.getActiveCount())
- .as("should not have any token being refreshed")
- .isEqualTo(0);
- assertThat(executor.getCompletedTaskCount())
- .as("should not have any expired token that required a refresh")
- .isEqualTo(0);
+ assertThat(validatingSigner.icebergSigner)
+ .extracting("authManager")
+ .extracting("refreshExecutor")
+ .asInstanceOf(type(ScheduledThreadPoolExecutor.class))
+ .satisfies(
+ executor -> {
+ assertThat(executor.getPoolSize()).isEqualTo(1);
+ assertThat(executor.getQueue())
+ .as("should only have a single token scheduled for refresh")
+ .hasSize(1);
+ assertThat(executor.getActiveCount())
+ .as("should not have any token being refreshed")
+ .isEqualTo(0);
+ assertThat(executor.getCompletedTaskCount())
+ .as("should not have any expired token that required a refresh")
+ .isEqualTo(0);
+ });
if (null != httpServer) {
httpServer.stop();
diff --git a/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3V4RestSignerClient.java b/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3V4RestSignerClient.java
new file mode 100644
index 000000000000..821e16443548
--- /dev/null
+++ b/aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3V4RestSignerClient.java
@@ -0,0 +1,129 @@
+/*
+ * 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.iceberg.aws.s3.signer;
+
+import static org.apache.iceberg.aws.s3.signer.S3V4RestSignerClient.S3_SIGNER_URI;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.InstanceOfAssertFactories.type;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.iceberg.rest.RESTClient;
+import org.apache.iceberg.rest.auth.AuthProperties;
+import org.apache.iceberg.rest.auth.AuthSession;
+import org.apache.iceberg.rest.auth.OAuth2Properties;
+import org.apache.iceberg.rest.auth.OAuth2Util;
+import org.apache.iceberg.rest.responses.OAuthTokenResponse;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mockito;
+
+class TestS3V4RestSignerClient {
+
+ @BeforeAll
+ static void beforeAll() {
+ S3V4RestSignerClient.httpClient = Mockito.mock(RESTClient.class);
+ when(S3V4RestSignerClient.httpClient.withAuthSession(Mockito.any()))
+ .thenReturn(S3V4RestSignerClient.httpClient);
+ when(S3V4RestSignerClient.httpClient.postForm(
+ Mockito.anyString(),
+ Mockito.eq(
+ Map.of(
+ "grant_type",
+ "client_credentials",
+ "client_id",
+ "user",
+ "client_secret",
+ "12345",
+ "scope",
+ "sign")),
+ Mockito.eq(OAuthTokenResponse.class),
+ Mockito.anyMap(),
+ Mockito.any()))
+ .thenReturn(
+ OAuthTokenResponse.builder().withToken("token").withTokenType("Bearer").build());
+ }
+
+ @AfterAll
+ static void afterAll() {
+ S3V4RestSignerClient.httpClient = null;
+ }
+
+ @ParameterizedTest
+ @MethodSource("validOAuth2Properties")
+ void authSessionOAuth2(Map properties, String expectedToken) throws Exception {
+ try (S3V4RestSignerClient client =
+ ImmutableS3V4RestSignerClient.builder().properties(properties).build();
+ AuthSession authSession = client.authSession()) {
+
+ if (expectedToken == null) {
+ assertThat(authSession).isInstanceOf(AuthSession.class);
+ } else {
+ assertThat(authSession)
+ .asInstanceOf(type(OAuth2Util.AuthSession.class))
+ .extracting(OAuth2Util.AuthSession::headers)
+ .satisfies(
+ headers ->
+ assertThat(headers).containsEntry("Authorization", "Bearer " + expectedToken));
+ }
+ }
+ }
+
+ public static Stream validOAuth2Properties() {
+ return Stream.of(
+ // No OAuth2 data
+ Arguments.of(Map.of(S3_SIGNER_URI, "https://signer.com"), null),
+ // Token only
+ Arguments.of(
+ Map.of(
+ S3_SIGNER_URI,
+ "https://signer.com",
+ AuthProperties.AUTH_TYPE,
+ AuthProperties.AUTH_TYPE_OAUTH2,
+ OAuth2Properties.TOKEN,
+ "token"),
+ "token"),
+ // Credential only: expect a token to be fetched
+ Arguments.of(
+ Map.of(
+ S3_SIGNER_URI,
+ "https://signer.com",
+ AuthProperties.AUTH_TYPE,
+ AuthProperties.AUTH_TYPE_OAUTH2,
+ OAuth2Properties.CREDENTIAL,
+ "user:12345"),
+ "token"),
+ // Token and credential: should use token as is, not fetch a new one
+ Arguments.of(
+ Map.of(
+ S3_SIGNER_URI,
+ "https://signer.com",
+ AuthProperties.AUTH_TYPE,
+ AuthProperties.AUTH_TYPE_OAUTH2,
+ OAuth2Properties.TOKEN,
+ "token",
+ OAuth2Properties.CREDENTIAL,
+ "user:12345"),
+ "token"));
+ }
+}
diff --git a/core/src/main/java/org/apache/iceberg/rest/HTTPClient.java b/core/src/main/java/org/apache/iceberg/rest/HTTPClient.java
index dfb1d5827ef9..72c8dc7ab228 100644
--- a/core/src/main/java/org/apache/iceberg/rest/HTTPClient.java
+++ b/core/src/main/java/org/apache/iceberg/rest/HTTPClient.java
@@ -40,7 +40,6 @@
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
-import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.impl.EnglishReasonPhraseCatalog;
@@ -48,8 +47,6 @@
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.io.CloseMode;
import org.apache.iceberg.IcebergBuild;
-import org.apache.iceberg.common.DynConstructors;
-import org.apache.iceberg.common.DynMethods;
import org.apache.iceberg.exceptions.RESTException;
import org.apache.iceberg.relocated.com.google.common.annotations.VisibleForTesting;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
@@ -65,9 +62,6 @@
public class HTTPClient extends BaseHTTPClient {
private static final Logger LOG = LoggerFactory.getLogger(HTTPClient.class);
- private static final String SIGV4_ENABLED = "rest.sigv4-enabled";
- private static final String SIGV4_REQUEST_INTERCEPTOR_IMPL =
- "org.apache.iceberg.aws.RESTSigV4Signer";
@VisibleForTesting static final String CLIENT_VERSION_HEADER = "X-Client-Version";
@VisibleForTesting
@@ -96,7 +90,6 @@ private HTTPClient(
CredentialsProvider proxyCredsProvider,
Map baseHeaders,
ObjectMapper objectMapper,
- HttpRequestInterceptor requestInterceptor,
Map properties,
HttpClientConnectionManager connectionManager,
AuthSession session) {
@@ -109,10 +102,6 @@ private HTTPClient(
clientBuilder.setConnectionManager(connectionManager);
- if (requestInterceptor != null) {
- clientBuilder.addRequestInterceptorLast(requestInterceptor);
- }
-
int maxRetries = PropertyUtil.propertyAsInt(properties, REST_MAX_RETRIES, 5);
clientBuilder.setRetryStrategy(new ExponentialHttpRequestRetryStrategy(maxRetries));
@@ -339,41 +328,6 @@ public void close() throws IOException {
}
}
- @VisibleForTesting
- static HttpRequestInterceptor loadInterceptorDynamically(
- String impl, Map properties) {
- HttpRequestInterceptor instance;
-
- DynConstructors.Ctor ctor;
- try {
- ctor =
- DynConstructors.builder(HttpRequestInterceptor.class)
- .loader(HTTPClient.class.getClassLoader())
- .impl(impl)
- .buildChecked();
- } catch (NoSuchMethodException e) {
- throw new IllegalArgumentException(
- String.format(
- "Cannot initialize RequestInterceptor, missing no-arg constructor: %s", impl),
- e);
- }
-
- try {
- instance = ctor.newInstance();
- } catch (ClassCastException e) {
- throw new IllegalArgumentException(
- String.format("Cannot initialize, %s does not implement RequestInterceptor", impl), e);
- }
-
- DynMethods.builder("initialize")
- .hiddenImpl(impl, Map.class)
- .orNoop()
- .build(instance)
- .invoke(properties);
-
- return instance;
- }
-
static HttpClientConnectionManager configureConnectionManager(Map properties) {
PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder =
PoolingHttpClientConnectionManagerBuilder.create();
@@ -489,12 +443,6 @@ public HTTPClient build() {
withHeader(CLIENT_VERSION_HEADER, IcebergBuild.fullVersion());
withHeader(CLIENT_GIT_COMMIT_SHORT_HEADER, IcebergBuild.gitCommitShortId());
- HttpRequestInterceptor interceptor = null;
-
- if (PropertyUtil.propertyAsBoolean(properties, SIGV4_ENABLED, false)) {
- interceptor = loadInterceptorDynamically(SIGV4_REQUEST_INTERCEPTOR_IMPL, properties);
- }
-
if (this.proxyCredentialsProvider != null) {
Preconditions.checkNotNull(
proxy, "Invalid http client proxy for proxy credentials provider: null");
@@ -506,7 +454,6 @@ public HTTPClient build() {
proxyCredentialsProvider,
baseHeaders,
mapper,
- interceptor,
properties,
configureConnectionManager(properties),
authSession);
diff --git a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
index d9542bf4a547..ab56b113d66e 100644
--- a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
+++ b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java
@@ -18,23 +18,15 @@
*/
package org.apache.iceberg.rest;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.Caffeine;
-import com.github.benmanes.caffeine.cache.RemovalListener;
import java.io.Closeable;
import java.io.IOException;
import java.io.UncheckedIOException;
-import java.time.Duration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;
-import java.util.function.Supplier;
import org.apache.iceberg.BaseTable;
import org.apache.iceberg.CatalogProperties;
import org.apache.iceberg.CatalogUtil;
@@ -70,11 +62,9 @@
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableSet;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.relocated.com.google.common.collect.Maps;
-import org.apache.iceberg.rest.auth.AuthConfig;
-import org.apache.iceberg.rest.auth.DefaultAuthSession;
-import org.apache.iceberg.rest.auth.OAuth2Properties;
-import org.apache.iceberg.rest.auth.OAuth2Util;
-import org.apache.iceberg.rest.auth.OAuth2Util.AuthSession;
+import org.apache.iceberg.rest.auth.AuthManager;
+import org.apache.iceberg.rest.auth.AuthManagers;
+import org.apache.iceberg.rest.auth.AuthSession;
import org.apache.iceberg.rest.requests.CommitTransactionRequest;
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
import org.apache.iceberg.rest.requests.CreateTableRequest;
@@ -92,12 +82,9 @@
import org.apache.iceberg.rest.responses.ListTablesResponse;
import org.apache.iceberg.rest.responses.LoadTableResponse;
import org.apache.iceberg.rest.responses.LoadViewResponse;
-import org.apache.iceberg.rest.responses.OAuthTokenResponse;
import org.apache.iceberg.rest.responses.UpdateNamespacePropertiesResponse;
import org.apache.iceberg.util.EnvironmentUtil;
-import org.apache.iceberg.util.Pair;
import org.apache.iceberg.util.PropertyUtil;
-import org.apache.iceberg.util.ThreadPools;
import org.apache.iceberg.view.BaseView;
import org.apache.iceberg.view.ImmutableSQLViewRepresentation;
import org.apache.iceberg.view.ImmutableViewVersion;
@@ -120,20 +107,6 @@ public class RESTSessionCatalog extends BaseViewSessionCatalog
// server supports view endpoints but doesn't send the "endpoints" field in the ConfigResponse
static final String VIEW_ENDPOINTS_SUPPORTED = "view-endpoints-supported";
public static final String REST_PAGE_SIZE = "rest-page-size";
- private static final List TOKEN_PREFERENCE_ORDER =
- ImmutableList.of(
- OAuth2Properties.ID_TOKEN_TYPE,
- OAuth2Properties.ACCESS_TOKEN_TYPE,
- OAuth2Properties.JWT_TOKEN_TYPE,
- OAuth2Properties.SAML2_TOKEN_TYPE,
- OAuth2Properties.SAML1_TOKEN_TYPE);
-
- // Auth-related properties that are allowed to be passed to the table session
- private static final Set TABLE_SESSION_ALLOW_LIST =
- ImmutableSet.builder()
- .add(OAuth2Properties.TOKEN)
- .addAll(TOKEN_PREFERENCE_ORDER)
- .build();
// these default endpoints must not be updated in order to maintain backwards compatibility with
// legacy servers
@@ -169,11 +142,9 @@ public class RESTSessionCatalog extends BaseViewSessionCatalog
private final Function