From 797370492f0f55fe62376a669ec21db205d620b8 Mon Sep 17 00:00:00 2001 From: Eduard Tudenhoefner Date: Tue, 16 Apr 2024 18:00:42 +0200 Subject: [PATCH] Core: Introduce AuthConfig --- .../aws/s3/signer/S3V4RestSignerClient.java | 26 +-- .../iceberg/rest/RESTSessionCatalog.java | 9 +- .../apache/iceberg/rest/auth/AuthConfig.java | 72 +++++++++ .../apache/iceberg/rest/auth/OAuth2Util.java | 151 +++++++++--------- 4 files changed, 166 insertions(+), 92 deletions(-) create mode 100644 core/src/main/java/org/apache/iceberg/rest/auth/AuthConfig.java 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 cdbdfb3d869e..806c52420f89 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 @@ -42,6 +42,7 @@ 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.OAuth2Properties; import org.apache.iceberg.rest.auth.OAuth2Util; import org.apache.iceberg.rest.auth.OAuth2Util.AuthSession; @@ -213,12 +214,13 @@ private AuthSession authSession() { expiresAtMillis(properties()), new AuthSession( ImmutableMap.of(), - token, - null, - credential(), - SCOPE, - oauth2ServerUri(), - optionalOAuthParams()))); + AuthConfig.builder() + .token(token) + .credential(credential()) + .scope(SCOPE) + .oauth2ServerUri(oauth2ServerUri()) + .optionalOAuthParams(optionalOAuthParams()) + .build()))); } if (credentialProvided()) { @@ -229,12 +231,12 @@ private AuthSession authSession() { AuthSession session = new AuthSession( ImmutableMap.of(), - null, - null, - credential(), - SCOPE, - oauth2ServerUri(), - optionalOAuthParams()); + AuthConfig.builder() + .credential(credential()) + .scope(SCOPE) + .oauth2ServerUri(oauth2ServerUri()) + .optionalOAuthParams(optionalOAuthParams()) + .build()); long startTimeMillis = System.currentTimeMillis(); OAuthTokenResponse authResponse = OAuth2Util.fetchToken( 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 dcf92289df2e..da40d4c3ae31 100644 --- a/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java +++ b/core/src/main/java/org/apache/iceberg/rest/RESTSessionCatalog.java @@ -70,6 +70,7 @@ import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap; 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.OAuth2Properties; import org.apache.iceberg.rest.auth.OAuth2Util; import org.apache.iceberg.rest.auth.OAuth2Util.AuthSession; @@ -219,7 +220,13 @@ public void initialize(String name, Map unresolved) { String token = mergedProps.get(OAuth2Properties.TOKEN); this.catalogAuth = new AuthSession( - baseHeaders, null, null, credential, scope, oauth2ServerUri, optionalOAuthParams); + baseHeaders, + AuthConfig.builder() + .credential(credential) + .scope(scope) + .oauth2ServerUri(oauth2ServerUri) + .optionalOAuthParams(optionalOAuthParams) + .build()); if (authResponse != null) { this.catalogAuth = AuthSession.fromTokenResponse( diff --git a/core/src/main/java/org/apache/iceberg/rest/auth/AuthConfig.java b/core/src/main/java/org/apache/iceberg/rest/auth/AuthConfig.java new file mode 100644 index 000000000000..275884e1184a --- /dev/null +++ b/core/src/main/java/org/apache/iceberg/rest/auth/AuthConfig.java @@ -0,0 +1,72 @@ +/* + * 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.rest.auth; + +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.iceberg.rest.ResourcePaths; +import org.immutables.value.Value; + +/** + * The purpose of this class is to hold configuration options for {@link + * org.apache.iceberg.rest.auth.OAuth2Util.AuthSession}. + */ +@Value.Style(redactedMask = "****") +@SuppressWarnings("ImmutablesStyle") +@Value.Immutable +public interface AuthConfig { + @Nullable + @Value.Redacted + String token(); + + @Nullable + String tokenType(); + + @Nullable + @Value.Redacted + String credential(); + + @Value.Default + default String scope() { + return OAuth2Properties.CATALOG_SCOPE; + } + + @Value.Lazy + @Nullable + default Long expiresAtMillis() { + return OAuth2Util.expiresAtMillis(token()); + } + + @Value.Default + default boolean keepRefreshed() { + return true; + } + + @Nullable + @Value.Default + default String oauth2ServerUri() { + return ResourcePaths.tokens(); + } + + Map optionalOAuthParams(); + + static ImmutableAuthConfig.Builder builder() { + return ImmutableAuthConfig.builder(); + } +} diff --git a/core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java b/core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java index 9e36694508d9..2283aba7d02d 100644 --- a/core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java +++ b/core/src/main/java/org/apache/iceberg/rest/auth/OAuth2Util.java @@ -458,32 +458,11 @@ public static class AuthSession { private static final long MAX_REFRESH_WINDOW_MILLIS = 300_000; // 5 minutes private static final long MIN_REFRESH_WAIT_MILLIS = 10; private volatile Map headers; - private volatile String token; - private volatile String tokenType; - private volatile Long expiresAtMillis; - private final String credential; - private final String scope; - private volatile boolean keepRefreshed = true; - private final String oauth2ServerUri; + private volatile AuthConfig config; - private Map optionalOAuthParams = ImmutableMap.of(); - - public AuthSession( - Map baseHeaders, - String token, - String tokenType, - String credential, - String scope, - String oauth2ServerUri, - Map optionalOAuthParams) { - this.headers = RESTUtil.merge(baseHeaders, authHeaders(token)); - this.token = token; - this.tokenType = tokenType; - this.expiresAtMillis = OAuth2Util.expiresAtMillis(token); - this.credential = credential; - this.scope = scope; - this.oauth2ServerUri = oauth2ServerUri; - this.optionalOAuthParams = optionalOAuthParams; + public AuthSession(Map baseHeaders, AuthConfig config) { + this.headers = RESTUtil.merge(baseHeaders, authHeaders(config.token())); + this.config = config; } /** @deprecated since 1.5.0, will be removed in 1.6.0 */ @@ -494,13 +473,14 @@ public AuthSession( String tokenType, String credential, String scope) { - this.headers = RESTUtil.merge(baseHeaders, authHeaders(token)); - this.token = token; - this.tokenType = tokenType; - this.expiresAtMillis = OAuth2Util.expiresAtMillis(token); - this.credential = credential; - this.scope = scope; - this.oauth2ServerUri = ResourcePaths.tokens(); + this( + baseHeaders, + AuthConfig.builder() + .token(token) + .tokenType(tokenType) + .credential(credential) + .scope(scope) + .build()); } /** @deprecated since 1.6.0, will be removed in 1.7.0 */ @@ -512,14 +492,15 @@ public AuthSession( String credential, String scope, String oauth2ServerUri) { - this.headers = RESTUtil.merge(baseHeaders, authHeaders(token)); - this.token = token; - this.tokenType = tokenType; - this.expiresAtMillis = OAuth2Util.expiresAtMillis(token); - this.credential = credential; - this.scope = scope; - this.oauth2ServerUri = oauth2ServerUri; - this.optionalOAuthParams = ImmutableMap.of(); + this( + baseHeaders, + AuthConfig.builder() + .token(token) + .tokenType(tokenType) + .credential(credential) + .scope(scope) + .oauth2ServerUri(oauth2ServerUri) + .build()); } public Map headers() { @@ -527,35 +508,39 @@ public Map headers() { } public String token() { - return token; + return config.token(); } public String tokenType() { - return tokenType; + return config.tokenType(); } public Long expiresAtMillis() { - return expiresAtMillis; + return config.expiresAtMillis(); } public String scope() { - return scope; + return config.scope(); } - public void stopRefreshing() { - this.keepRefreshed = false; + public synchronized void stopRefreshing() { + this.config = ImmutableAuthConfig.copyOf(config).withKeepRefreshed(false); } public String credential() { - return credential; + return config.credential(); } public String oauth2ServerUri() { - return oauth2ServerUri; + return config.oauth2ServerUri(); } public Map optionalOAuthParams() { - return optionalOAuthParams; + return config.optionalOAuthParams(); + } + + public AuthConfig config() { + return config; } @VisibleForTesting @@ -569,14 +554,7 @@ static void setTokenRefreshNumRetries(int retries) { * @return A new {@link AuthSession} with empty headers. */ public static AuthSession empty() { - return new AuthSession( - ImmutableMap.of(), - null, - null, - null, - OAuth2Properties.CATALOG_SCOPE, - null, - ImmutableMap.of()); + return new AuthSession(ImmutableMap.of(), AuthConfig.builder().build()); } /** @@ -586,7 +564,7 @@ public static AuthSession empty() { * @return interval to wait before calling refresh again, or null if no refresh is needed */ public Pair refresh(RESTClient client) { - if (token != null && keepRefreshed) { + if (token() != null && config.keepRefreshed()) { AtomicReference ref = new AtomicReference<>(null); boolean isSuccessful = Tasks.foreach(ref) @@ -612,10 +590,13 @@ public Pair refresh(RESTClient client) { } OAuthTokenResponse response = ref.get(); - this.token = response.token(); - this.tokenType = response.issuedTokenType(); - this.expiresAtMillis = OAuth2Util.expiresAtMillis(token); - this.headers = RESTUtil.merge(headers, authHeaders(token)); + this.config = + AuthConfig.builder() + .from(config()) + .token(response.token()) + .tokenType(response.issuedTokenType()) + .build(); + this.headers = RESTUtil.merge(headers, authHeaders(config.token())); if (response.expiresInSeconds() != null) { return Pair.of(response.expiresInSeconds(), TimeUnit.SECONDS); @@ -626,21 +607,34 @@ public Pair refresh(RESTClient client) { } private OAuthTokenResponse refreshCurrentToken(RESTClient client) { - if (null != expiresAtMillis && expiresAtMillis <= System.currentTimeMillis()) { + if (null != expiresAtMillis() && expiresAtMillis() <= System.currentTimeMillis()) { // the token has already expired, attempt to refresh using the credential return refreshExpiredToken(client); } else { // attempt a normal refresh return refreshToken( - client, headers(), token, tokenType, scope, oauth2ServerUri, optionalOAuthParams); + client, + headers(), + token(), + tokenType(), + scope(), + oauth2ServerUri(), + optionalOAuthParams()); } } private OAuthTokenResponse refreshExpiredToken(RESTClient client) { - if (credential != null) { - Map basicHeaders = RESTUtil.merge(headers(), basicAuthHeaders(credential)); + if (credential() != null) { + Map basicHeaders = + RESTUtil.merge(headers(), basicAuthHeaders(credential())); return refreshToken( - client, basicHeaders, token, tokenType, scope, oauth2ServerUri, optionalOAuthParams); + client, + basicHeaders, + token(), + tokenType(), + scope(), + oauth2ServerUri(), + optionalOAuthParams()); } return null; @@ -693,12 +687,11 @@ public static AuthSession fromAccessToken( AuthSession session = new AuthSession( parent.headers(), - token, - OAuth2Properties.ACCESS_TOKEN_TYPE, - parent.credential(), - parent.scope(), - parent.oauth2ServerUri(), - parent.optionalOAuthParams()); + AuthConfig.builder() + .from(parent.config()) + .token(token) + .tokenType(OAuth2Properties.ACCESS_TOKEN_TYPE) + .build()); long startTimeMillis = System.currentTimeMillis(); Long expiresAtMillis = session.expiresAtMillis(); @@ -766,12 +759,12 @@ private static AuthSession fromTokenResponse( AuthSession session = new AuthSession( parent.headers(), - response.token(), - response.issuedTokenType(), - credential, - parent.scope(), - parent.oauth2ServerUri(), - parent.optionalOAuthParams()); + AuthConfig.builder() + .from(parent.config()) + .token(response.token()) + .tokenType(response.issuedTokenType()) + .credential(credential) + .build()); Long expiresAtMillis = session.expiresAtMillis(); if (null == expiresAtMillis && response.expiresInSeconds() != null) {