diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogInternalOptions.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogInternalOptions.java index 722010923c46..4749ee23e6e1 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogInternalOptions.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalogInternalOptions.java @@ -23,19 +23,10 @@ /** Internal options for REST Catalog. */ public class RESTCatalogInternalOptions { + public static final ConfigOption PREFIX = ConfigOptions.key("prefix") .stringType() .noDefaultValue() .withDescription("REST Catalog uri's prefix."); - public static final ConfigOption CREDENTIALS_PROVIDER = - ConfigOptions.key("credentials-provider") - .stringType() - .noDefaultValue() - .withDescription("REST Catalog auth credentials provider."); - public static final ConfigOption DATABASE_COMMENT = - ConfigOptions.key("comment") - .stringType() - .defaultValue(null) - .withDescription("REST Catalog database comment."); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthProvider.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthProvider.java new file mode 100644 index 000000000000..26c9490dc94d --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthProvider.java @@ -0,0 +1,81 @@ +/* + * 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.paimon.rest.auth; + +import org.apache.paimon.options.Options; +import org.apache.paimon.rest.RESTCatalogOptions; +import org.apache.paimon.utils.StringUtils; + +import java.util.Map; +import java.util.Optional; + +import static org.apache.paimon.rest.RESTCatalogOptions.TOKEN_EXPIRATION_TIME; +import static org.apache.paimon.rest.RESTCatalogOptions.TOKEN_PROVIDER_PATH; + +/** Authentication provider. */ +public interface AuthProvider { + + Map authHeader(); + + boolean refresh(); + + default boolean supportRefresh() { + return false; + } + + default boolean keepRefreshed() { + return false; + } + + default boolean willSoonExpire() { + return false; + } + + default Optional expiresAtMillis() { + return Optional.empty(); + } + + default Optional expiresInMills() { + return Optional.empty(); + } + + static AuthProvider create(Options options) { + if (options.getOptional(RESTCatalogOptions.TOKEN_PROVIDER_PATH).isPresent()) { + if (!options.getOptional(TOKEN_PROVIDER_PATH).isPresent()) { + throw new IllegalArgumentException(TOKEN_PROVIDER_PATH.key() + " is required"); + } + String tokenFilePath = options.get(TOKEN_PROVIDER_PATH); + if (options.getOptional(TOKEN_EXPIRATION_TIME).isPresent()) { + long tokenExpireInMills = options.get(TOKEN_EXPIRATION_TIME).toMillis(); + return new BearTokenFileAuthProvider(tokenFilePath, tokenExpireInMills); + + } else { + return new BearTokenFileAuthProvider(tokenFilePath); + } + } else { + if (options.getOptional(RESTCatalogOptions.TOKEN) + .map(StringUtils::isNullOrWhitespaceOnly) + .orElse(true)) { + throw new IllegalArgumentException( + RESTCatalogOptions.TOKEN.key() + " is required and not empty"); + } + return new BearTokenAuthProvider(options.get(RESTCatalogOptions.TOKEN)); + } + } +} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java index 470af7f6f699..5f77002c0d48 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java @@ -20,7 +20,6 @@ import org.apache.paimon.annotation.VisibleForTesting; import org.apache.paimon.options.Options; -import org.apache.paimon.rest.RESTCatalog; import org.apache.paimon.rest.RESTUtil; import org.slf4j.Logger; @@ -34,37 +33,38 @@ import static org.apache.paimon.rest.RESTCatalog.HEADER_PREFIX; import static org.apache.paimon.rest.RESTUtil.extractPrefixMap; -/** Auth session. */ +/** Authentication session. */ public class AuthSession { - static final int TOKEN_REFRESH_NUM_RETRIES = 5; + static final int REFRESH_NUM_RETRIES = 5; static final long MIN_REFRESH_WAIT_MILLIS = 10; static final long MAX_REFRESH_WINDOW_MILLIS = 300_000; // 5 minutes - private static final Logger log = LoggerFactory.getLogger(AuthSession.class); - private final CredentialsProvider credentialsProvider; + private static final Logger LOG = LoggerFactory.getLogger(AuthSession.class); + + private final AuthProvider authProvider; private volatile Map headers; - public AuthSession(Map headers, CredentialsProvider credentialsProvider) { - this.credentialsProvider = credentialsProvider; - this.headers = RESTUtil.merge(headers, this.credentialsProvider.authHeader()); + public AuthSession(Map headers, AuthProvider authProvider) { + this.authProvider = authProvider; + this.headers = RESTUtil.merge(headers, this.authProvider.authHeader()); } - public static AuthSession fromRefreshCredentialsProvider( + public static AuthSession fromRefreshAuthProvider( ScheduledExecutorService executor, Map headers, - CredentialsProvider credentialsProvider) { - AuthSession session = new AuthSession(headers, credentialsProvider); + AuthProvider authProvider) { + AuthSession session = new AuthSession(headers, authProvider); long startTimeMillis = System.currentTimeMillis(); - Optional expiresAtMillisOpt = credentialsProvider.expiresAtMillis(); + Optional expiresAtMillisOpt = authProvider.expiresAtMillis(); - // when init session if credentials expire time is in the past, refresh it and update + // when init session if token expire time is in the past, refresh it and update // expiresAtMillis if (expiresAtMillisOpt.isPresent() && expiresAtMillisOpt.get() <= startTimeMillis) { boolean refreshSuccessful = session.refresh(); if (refreshSuccessful) { - expiresAtMillisOpt = session.credentialsProvider.expiresAtMillis(); + expiresAtMillisOpt = session.authProvider.expiresAtMillis(); } } @@ -76,21 +76,20 @@ public static AuthSession fromRefreshCredentialsProvider( } public Map getHeaders() { - if (this.credentialsProvider.keepRefreshed() && this.credentialsProvider.willSoonExpire()) { + if (this.authProvider.keepRefreshed() && this.authProvider.willSoonExpire()) { refresh(); } return headers; } public Boolean refresh() { - if (this.credentialsProvider.supportRefresh() - && this.credentialsProvider.keepRefreshed() - && this.credentialsProvider.expiresInMills().isPresent()) { - boolean isSuccessful = this.credentialsProvider.refresh(); + if (this.authProvider.supportRefresh() + && this.authProvider.keepRefreshed() + && this.authProvider.expiresInMills().isPresent()) { + boolean isSuccessful = this.authProvider.refresh(); if (isSuccessful) { Map currentHeaders = this.headers; - this.headers = - RESTUtil.merge(currentHeaders, this.credentialsProvider.authHeader()); + this.headers = RESTUtil.merge(currentHeaders, this.authProvider.authHeader()); } return isSuccessful; } @@ -119,44 +118,45 @@ private static void scheduleTokenRefresh( AuthSession session, long expiresAtMillis, int retryTimes) { - if (retryTimes < TOKEN_REFRESH_NUM_RETRIES) { + if (retryTimes < REFRESH_NUM_RETRIES) { long expiresInMillis = expiresAtMillis - System.currentTimeMillis(); long timeToWait = getTimeToWaitByExpiresInMills(expiresInMillis); executor.schedule( - () -> { - long refreshStartTime = System.currentTimeMillis(); - boolean isSuccessful = session.refresh(); - if (isSuccessful) { - scheduleTokenRefresh( - executor, - session, - refreshStartTime - + session.credentialsProvider.expiresInMills().get(), - 0); - } else { - scheduleTokenRefresh( - executor, session, expiresAtMillis, retryTimes + 1); - } - }, + () -> doRefresh(executor, session, expiresAtMillis, retryTimes), timeToWait, TimeUnit.MILLISECONDS); } else { - log.warn("Failed to refresh token after {} retries.", TOKEN_REFRESH_NUM_RETRIES); + LOG.warn("Failed to refresh token after {} retries.", REFRESH_NUM_RETRIES); + } + } + + private static void doRefresh( + ScheduledExecutorService executor, + AuthSession session, + long expiresAtMillis, + int retryTimes) { + long refreshStartTime = System.currentTimeMillis(); + boolean isSuccessful = session.refresh(); + if (isSuccessful) { + scheduleTokenRefresh( + executor, + session, + refreshStartTime + session.authProvider.expiresInMills().get(), + 0); + } else { + scheduleTokenRefresh(executor, session, expiresAtMillis, retryTimes + 1); } } public static AuthSession createAuthSession( Options options, ScheduledExecutorService refreshExecutor) { Map baseHeader = extractPrefixMap(options, HEADER_PREFIX); - CredentialsProvider credentialsProvider = - CredentialsProviderFactory.createCredentialsProvider( - options, RESTCatalog.class.getClassLoader()); - if (credentialsProvider.keepRefreshed()) { - return AuthSession.fromRefreshCredentialsProvider( - refreshExecutor, baseHeader, credentialsProvider); + AuthProvider authProvider = AuthProvider.create(options); + if (authProvider.keepRefreshed()) { + return AuthSession.fromRefreshAuthProvider(refreshExecutor, baseHeader, authProvider); } else { - return new AuthSession(baseHeader, credentialsProvider); + return new AuthSession(baseHeader, authProvider); } } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BaseBearTokenCredentialsProvider.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenAuthProvider.java similarity index 77% rename from paimon-core/src/main/java/org/apache/paimon/rest/auth/BaseBearTokenCredentialsProvider.java rename to paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenAuthProvider.java index d3df87826164..73e5081a5e88 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BaseBearTokenCredentialsProvider.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenAuthProvider.java @@ -22,16 +22,29 @@ import java.util.Map; -/** Base bear token credentials provider. */ -public abstract class BaseBearTokenCredentialsProvider implements CredentialsProvider { +/** Auth provider for bear token. */ +public class BearTokenAuthProvider implements AuthProvider { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String BEARER_PREFIX = "Bearer "; + protected String token; + + public BearTokenAuthProvider(String token) { + this.token = token; + } + + public String token() { + return token; + } + @Override public Map authHeader() { - return ImmutableMap.of(AUTHORIZATION_HEADER, BEARER_PREFIX + token()); + return ImmutableMap.of(AUTHORIZATION_HEADER, BEARER_PREFIX + token); } - abstract String token(); + @Override + public boolean refresh() { + return true; + } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenCredentialsProvider.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenCredentialsProvider.java deleted file mode 100644 index 89228fe10b28..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenCredentialsProvider.java +++ /dev/null @@ -1,39 +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.paimon.rest.auth; - -/** credentials provider for bear token. */ -public class BearTokenCredentialsProvider extends BaseBearTokenCredentialsProvider { - - private final String token; - - public BearTokenCredentialsProvider(String token) { - this.token = token; - } - - @Override - String token() { - return this.token; - } - - @Override - public boolean refresh() { - return true; - } -} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenCredentialsProviderFactory.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenCredentialsProviderFactory.java deleted file mode 100644 index e63ac5606b01..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenCredentialsProviderFactory.java +++ /dev/null @@ -1,43 +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.paimon.rest.auth; - -import org.apache.paimon.options.Options; -import org.apache.paimon.rest.RESTCatalogOptions; -import org.apache.paimon.utils.StringUtils; - -/** factory for create {@link BearTokenCredentialsProvider}. */ -public class BearTokenCredentialsProviderFactory implements CredentialsProviderFactory { - - @Override - public String identifier() { - return CredentialsProviderType.BEAR_TOKEN.name(); - } - - @Override - public CredentialsProvider create(Options options) { - if (options.getOptional(RESTCatalogOptions.TOKEN) - .map(StringUtils::isNullOrWhitespaceOnly) - .orElse(true)) { - throw new IllegalArgumentException( - RESTCatalogOptions.TOKEN.key() + " is required and not empty"); - } - return new BearTokenCredentialsProvider(options.get(RESTCatalogOptions.TOKEN)); - } -} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileCredentialsProvider.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileAuthProvider.java similarity index 81% rename from paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileCredentialsProvider.java rename to paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileAuthProvider.java index d479caa67fd0..a1ecb0b26cd7 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileCredentialsProvider.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileAuthProvider.java @@ -26,38 +26,33 @@ import java.io.UncheckedIOException; import java.util.Optional; -/** credentials provider for get bear token from file. */ -public class BearTokenFileCredentialsProvider extends BaseBearTokenCredentialsProvider { +/** Auth provider for get bear token from file. */ +public class BearTokenFileAuthProvider extends BearTokenAuthProvider { public static final double EXPIRED_FACTOR = 0.4; private final String tokenFilePath; - private String token; + private boolean keepRefreshed = false; private Long expiresAtMillis = null; private Long expiresInMills = null; - public BearTokenFileCredentialsProvider(String tokenFilePath) { + public BearTokenFileAuthProvider(String tokenFilePath) { + super(readToken(tokenFilePath)); this.tokenFilePath = tokenFilePath; - this.token = getTokenFromFile(); } - public BearTokenFileCredentialsProvider(String tokenFilePath, Long expiresInMills) { + public BearTokenFileAuthProvider(String tokenFilePath, Long expiresInMills) { this(tokenFilePath); this.keepRefreshed = true; this.expiresAtMillis = -1L; this.expiresInMills = expiresInMills; } - @Override - String token() { - return this.token; - } - @Override public boolean refresh() { long start = System.currentTimeMillis(); - String newToken = getTokenFromFile(); + String newToken = readToken(tokenFilePath); if (StringUtils.isNullOrWhitespaceOnly(newToken)) { return false; } @@ -96,9 +91,9 @@ public Optional expiresInMills() { return Optional.ofNullable(this.expiresInMills); } - private String getTokenFromFile() { + private static String readToken(String filePath) { try { - return FileIOUtils.readFileUtf8(new File(tokenFilePath)); + return FileIOUtils.readFileUtf8(new File(filePath)); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileCredentialsProviderFactory.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileCredentialsProviderFactory.java deleted file mode 100644 index a0fa6b405d62..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/BearTokenFileCredentialsProviderFactory.java +++ /dev/null @@ -1,48 +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.paimon.rest.auth; - -import org.apache.paimon.options.Options; - -import static org.apache.paimon.rest.RESTCatalogOptions.TOKEN_EXPIRATION_TIME; -import static org.apache.paimon.rest.RESTCatalogOptions.TOKEN_PROVIDER_PATH; - -/** factory for create {@link BearTokenCredentialsProvider}. */ -public class BearTokenFileCredentialsProviderFactory implements CredentialsProviderFactory { - - @Override - public String identifier() { - return CredentialsProviderType.BEAR_TOKEN_FILE.name(); - } - - @Override - public CredentialsProvider create(Options options) { - if (!options.getOptional(TOKEN_PROVIDER_PATH).isPresent()) { - throw new IllegalArgumentException(TOKEN_PROVIDER_PATH.key() + " is required"); - } - String tokenFilePath = options.get(TOKEN_PROVIDER_PATH); - if (options.getOptional(TOKEN_EXPIRATION_TIME).isPresent()) { - long tokenExpireInMills = options.get(TOKEN_EXPIRATION_TIME).toMillis(); - return new BearTokenFileCredentialsProvider(tokenFilePath, tokenExpireInMills); - - } else { - return new BearTokenFileCredentialsProvider(tokenFilePath); - } - } -} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProvider.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProvider.java deleted file mode 100644 index 7fe8008e5947..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProvider.java +++ /dev/null @@ -1,50 +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.paimon.rest.auth; - -import java.util.Map; -import java.util.Optional; - -/** Credentials provider. */ -public interface CredentialsProvider { - - Map authHeader(); - - boolean refresh(); - - default boolean supportRefresh() { - return false; - } - - default boolean keepRefreshed() { - return false; - } - - default boolean willSoonExpire() { - return false; - } - - default Optional expiresAtMillis() { - return Optional.empty(); - } - - default Optional expiresInMills() { - return Optional.empty(); - } -} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProviderFactory.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProviderFactory.java deleted file mode 100644 index 50c3564ad8c6..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProviderFactory.java +++ /dev/null @@ -1,54 +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.paimon.rest.auth; - -import org.apache.paimon.factories.Factory; -import org.apache.paimon.factories.FactoryUtil; -import org.apache.paimon.options.Options; -import org.apache.paimon.rest.RESTCatalogOptions; - -import static org.apache.paimon.rest.RESTCatalogInternalOptions.CREDENTIALS_PROVIDER; - -/** Factory for creating {@link CredentialsProvider}. */ -public interface CredentialsProviderFactory extends Factory { - - default CredentialsProvider create(Options options) { - throw new UnsupportedOperationException( - "Use create(context) for " + this.getClass().getSimpleName()); - } - - static CredentialsProvider createCredentialsProvider(Options options, ClassLoader classLoader) { - String credentialsProviderIdentifier = getCredentialsProviderTypeByConf(options).name(); - CredentialsProviderFactory credentialsProviderFactory = - FactoryUtil.discoverFactory( - classLoader, - CredentialsProviderFactory.class, - credentialsProviderIdentifier); - return credentialsProviderFactory.create(options); - } - - static CredentialsProviderType getCredentialsProviderTypeByConf(Options options) { - if (options.getOptional(CREDENTIALS_PROVIDER).isPresent()) { - return CredentialsProviderType.valueOf(options.get(CREDENTIALS_PROVIDER)); - } else if (options.getOptional(RESTCatalogOptions.TOKEN_PROVIDER_PATH).isPresent()) { - return CredentialsProviderType.BEAR_TOKEN_FILE; - } - return CredentialsProviderType.BEAR_TOKEN; - } -} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProviderType.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProviderType.java deleted file mode 100644 index 28c344d70eee..000000000000 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/CredentialsProviderType.java +++ /dev/null @@ -1,25 +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.paimon.rest.auth; - -/** Credentials provider type. */ -public enum CredentialsProviderType { - BEAR_TOKEN, - BEAR_TOKEN_FILE -} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java index 0b4554dc83f2..81c706cccba2 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.Optional; -import static org.apache.paimon.rest.RESTCatalogInternalOptions.DATABASE_COMMENT; +import static org.apache.paimon.catalog.Catalog.COMMENT_PROP; /** Response for getting database. */ @JsonIgnoreProperties(ignoreUnknown = true) @@ -75,17 +75,16 @@ public Map getOptions() { @Override public String name() { - return this.getName(); + return getName(); } @Override public Map options() { - return this.getOptions(); + return getOptions(); } @Override public Optional comment() { - return Optional.ofNullable( - this.options.getOrDefault(DATABASE_COMMENT.key(), DATABASE_COMMENT.defaultValue())); + return Optional.ofNullable(options.get(COMMENT_PROP)); } } diff --git a/paimon-core/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory b/paimon-core/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory index ff423bffd8df..7f221e517248 100644 --- a/paimon-core/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory +++ b/paimon-core/src/main/resources/META-INF/services/org.apache.paimon.factories.Factory @@ -37,6 +37,4 @@ org.apache.paimon.mergetree.compact.aggregate.factory.FieldRoaringBitmap64AggFac org.apache.paimon.mergetree.compact.aggregate.factory.FieldSumAggFactory org.apache.paimon.mergetree.compact.aggregate.factory.FieldThetaSketchAggFactory org.apache.paimon.rest.RESTCatalogFactory -org.apache.paimon.rest.auth.BearTokenCredentialsProviderFactory -org.apache.paimon.rest.auth.BearTokenFileCredentialsProviderFactory org.apache.paimon.iceberg.migrate.IcebergMigrateHadoopMetadataFactory diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 05078cf805f7..deaf6da81891 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -18,8 +18,8 @@ package org.apache.paimon.rest; -import org.apache.paimon.rest.auth.BearTokenCredentialsProvider; -import org.apache.paimon.rest.auth.CredentialsProvider; +import org.apache.paimon.rest.auth.AuthProvider; +import org.apache.paimon.rest.auth.BearTokenAuthProvider; import org.apache.paimon.rest.exceptions.BadRequestException; import org.apache.paimon.rest.responses.ErrorResponse; import org.apache.paimon.rest.responses.ErrorResponseResourceType; @@ -64,8 +64,8 @@ public void setUp() throws Exception { new ErrorResponse(ErrorResponseResourceType.DATABASE, "test", "test", 400)); httpClient = new HttpClient(httpClientOptions); httpClient.setErrorHandler(errorHandler); - CredentialsProvider credentialsProvider = new BearTokenCredentialsProvider(TOKEN); - headers = credentialsProvider.authHeader(); + AuthProvider authProvider = new BearTokenAuthProvider(TOKEN); + headers = authProvider.authHeader(); } @After diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java index 766cb09b0bdd..d64dd86da70c 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java @@ -58,7 +58,7 @@ import java.util.Map; import java.util.UUID; -import static org.apache.paimon.rest.RESTCatalogInternalOptions.DATABASE_COMMENT; +import static org.apache.paimon.catalog.Catalog.COMMENT_PROP; /** Mock REST message. */ public class MockRESTMessage { @@ -82,7 +82,7 @@ public static CreateDatabaseResponse createDatabaseResponse(String name) { public static GetDatabaseResponse getDatabaseResponse(String name) { Map options = new HashMap<>(); options.put("a", "b"); - options.put(DATABASE_COMMENT.key(), "comment"); + options.put(COMMENT_PROP, "comment"); return new GetDatabaseResponse(UUID.randomUUID().toString(), name, options); } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthProviderTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthProviderTest.java new file mode 100644 index 000000000000..5fdb1a9c40c8 --- /dev/null +++ b/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthProviderTest.java @@ -0,0 +1,82 @@ +/* + * 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.paimon.rest.auth; + +import org.apache.paimon.options.Options; +import org.apache.paimon.rest.RESTCatalogOptions; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.time.Duration; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +/** Test for {@link AuthProvider}. */ +public class AuthProviderTest { + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testCreateBearTokenSuccess() { + Options options = new Options(); + String token = UUID.randomUUID().toString(); + options.set(RESTCatalogOptions.TOKEN, token); + BearTokenAuthProvider authProvider = (BearTokenAuthProvider) AuthProvider.create(options); + assertEquals(token, authProvider.token()); + } + + @Test + public void testCreateBearTokenFail() { + Options options = new Options(); + assertThrows(IllegalArgumentException.class, () -> AuthProvider.create(options)); + } + + @Test + public void testCreateBearTokenFileSuccess() throws Exception { + Options options = new Options(); + String fileName = "token"; + File tokenFile = folder.newFile(fileName); + String token = UUID.randomUUID().toString(); + FileUtils.writeStringToFile(tokenFile, token); + options.set(RESTCatalogOptions.TOKEN_PROVIDER_PATH, tokenFile.getPath()); + BearTokenFileAuthProvider authProvider = + (BearTokenFileAuthProvider) AuthProvider.create(options); + assertEquals(token, authProvider.token()); + } + + @Test + public void testCreateRefreshBearTokenFileSuccess() throws Exception { + Options options = new Options(); + String fileName = "token"; + File tokenFile = folder.newFile(fileName); + String token = UUID.randomUUID().toString(); + FileUtils.writeStringToFile(tokenFile, token); + options.set(RESTCatalogOptions.TOKEN_PROVIDER_PATH, tokenFile.getPath()); + options.set(RESTCatalogOptions.TOKEN_EXPIRATION_TIME, Duration.ofSeconds(10L)); + BearTokenFileAuthProvider authProvider = + (BearTokenFileAuthProvider) AuthProvider.create(options); + assertEquals(token, authProvider.token()); + } +} diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java index fec749208273..b54c3103edf5 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java @@ -37,7 +37,7 @@ import static org.apache.paimon.rest.auth.AuthSession.MAX_REFRESH_WINDOW_MILLIS; import static org.apache.paimon.rest.auth.AuthSession.MIN_REFRESH_WAIT_MILLIS; -import static org.apache.paimon.rest.auth.AuthSession.TOKEN_REFRESH_NUM_RETRIES; +import static org.apache.paimon.rest.auth.AuthSession.REFRESH_NUM_RETRIES; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -53,8 +53,8 @@ public void testBearToken() { Map initialHeaders = new HashMap<>(); initialHeaders.put("k1", "v1"); initialHeaders.put("k2", "v2"); - CredentialsProvider credentialsProvider = new BearTokenCredentialsProvider(token); - AuthSession session = new AuthSession(initialHeaders, credentialsProvider); + AuthProvider authProvider = new BearTokenAuthProvider(token); + AuthSession session = new AuthSession(initialHeaders, authProvider); Map header = session.getHeaders(); assertEquals(header.get("Authorization"), "Bearer " + token); assertEquals(header.get("k1"), "v1"); @@ -65,21 +65,19 @@ public void testBearToken() { } @Test - public void testRefreshBearTokenFileCredentialsProvider() - throws IOException, InterruptedException { + public void testRefreshBearTokenFileAuthProvider() throws IOException, InterruptedException { String fileName = "token"; Pair tokenFile2Token = generateTokenAndWriteToFile(fileName); String token = tokenFile2Token.getRight(); File tokenFile = tokenFile2Token.getLeft(); Map initialHeaders = new HashMap<>(); long expiresInMillis = 1000L; - CredentialsProvider credentialsProvider = - new BearTokenFileCredentialsProvider(tokenFile.getPath(), expiresInMillis); + AuthProvider authProvider = + new BearTokenFileAuthProvider(tokenFile.getPath(), expiresInMillis); ScheduledExecutorService executor = ThreadPoolUtils.createScheduledThreadPool(1, "refresh-token"); AuthSession session = - AuthSession.fromRefreshCredentialsProvider( - executor, initialHeaders, credentialsProvider); + AuthSession.fromRefreshAuthProvider(executor, initialHeaders, authProvider); Map header = session.getHeaders(); assertEquals(header.get("Authorization"), "Bearer " + token); tokenFile.delete(); @@ -91,19 +89,17 @@ public void testRefreshBearTokenFileCredentialsProvider() } @Test - public void testRefreshCredentialsProviderIsSoonExpire() - throws IOException, InterruptedException { + public void testRefreshAuthProviderIsSoonExpire() throws IOException, InterruptedException { String fileName = "token"; Pair tokenFile2Token = generateTokenAndWriteToFile(fileName); String token = tokenFile2Token.getRight(); File tokenFile = tokenFile2Token.getLeft(); Map initialHeaders = new HashMap<>(); long expiresInMillis = 1000L; - CredentialsProvider credentialsProvider = - new BearTokenFileCredentialsProvider(tokenFile.getPath(), expiresInMillis); + AuthProvider authProvider = + new BearTokenFileAuthProvider(tokenFile.getPath(), expiresInMillis); AuthSession session = - AuthSession.fromRefreshCredentialsProvider( - null, initialHeaders, credentialsProvider); + AuthSession.fromRefreshAuthProvider(null, initialHeaders, authProvider); Map header = session.getHeaders(); assertEquals(header.get("Authorization"), "Bearer " + token); tokenFile.delete(); @@ -112,8 +108,7 @@ public void testRefreshCredentialsProviderIsSoonExpire() tokenFile = tokenFile2Token.getLeft(); FileUtils.writeStringToFile(tokenFile, token); Thread.sleep( - (long) (expiresInMillis * (1 - BearTokenFileCredentialsProvider.EXPIRED_FACTOR)) - + 10L); + (long) (expiresInMillis * (1 - BearTokenFileAuthProvider.EXPIRED_FACTOR)) + 10L); header = session.getHeaders(); assertEquals(header.get("Authorization"), "Bearer " + token); } @@ -121,23 +116,21 @@ public void testRefreshCredentialsProviderIsSoonExpire() @Test public void testRetryWhenRefreshFail() throws Exception { Map initialHeaders = new HashMap<>(); - CredentialsProvider credentialsProvider = - Mockito.mock(BearTokenFileCredentialsProvider.class); + AuthProvider authProvider = Mockito.mock(BearTokenFileAuthProvider.class); long expiresAtMillis = System.currentTimeMillis() - 1000L; - when(credentialsProvider.expiresAtMillis()).thenReturn(Optional.of(expiresAtMillis)); - when(credentialsProvider.expiresInMills()).thenReturn(Optional.of(50L)); - when(credentialsProvider.supportRefresh()).thenReturn(true); - when(credentialsProvider.keepRefreshed()).thenReturn(true); - when(credentialsProvider.refresh()).thenReturn(false); + when(authProvider.expiresAtMillis()).thenReturn(Optional.of(expiresAtMillis)); + when(authProvider.expiresInMills()).thenReturn(Optional.of(50L)); + when(authProvider.supportRefresh()).thenReturn(true); + when(authProvider.keepRefreshed()).thenReturn(true); + when(authProvider.refresh()).thenReturn(false); AuthSession session = - AuthSession.fromRefreshCredentialsProvider( - null, initialHeaders, credentialsProvider); + AuthSession.fromRefreshAuthProvider(null, initialHeaders, authProvider); AuthSession.scheduleTokenRefresh( ThreadPoolUtils.createScheduledThreadPool(1, "refresh-token"), session, expiresAtMillis); Thread.sleep(10_000L); - verify(credentialsProvider, Mockito.times(TOKEN_REFRESH_NUM_RETRIES + 1)).refresh(); + verify(authProvider, Mockito.times(REFRESH_NUM_RETRIES + 1)).refresh(); } @Test diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/auth/CredentialsProviderFactoryTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/auth/CredentialsProviderFactoryTest.java deleted file mode 100644 index e62a65a79aed..000000000000 --- a/paimon-core/src/test/java/org/apache/paimon/rest/auth/CredentialsProviderFactoryTest.java +++ /dev/null @@ -1,131 +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.paimon.rest.auth; - -import org.apache.paimon.options.Options; -import org.apache.paimon.rest.RESTCatalogOptions; - -import org.apache.commons.io.FileUtils; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.time.Duration; -import java.util.UUID; - -import static org.apache.paimon.rest.RESTCatalogInternalOptions.CREDENTIALS_PROVIDER; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; - -/** Test for {@link CredentialsProviderFactory}. */ -public class CredentialsProviderFactoryTest { - - @Rule public TemporaryFolder folder = new TemporaryFolder(); - - @Test - public void testCreateBearTokenCredentialsProviderSuccess() { - Options options = new Options(); - String token = UUID.randomUUID().toString(); - options.set(RESTCatalogOptions.TOKEN, token); - BearTokenCredentialsProvider credentialsProvider = - (BearTokenCredentialsProvider) - CredentialsProviderFactory.createCredentialsProvider( - options, this.getClass().getClassLoader()); - assertEquals(token, credentialsProvider.token()); - } - - @Test - public void testCreateBearTokenCredentialsProviderFail() { - Options options = new Options(); - assertThrows( - IllegalArgumentException.class, - () -> - CredentialsProviderFactory.createCredentialsProvider( - options, this.getClass().getClassLoader())); - } - - @Test - public void testCreateBearTokenFileCredentialsProviderSuccess() throws Exception { - Options options = new Options(); - String fileName = "token"; - File tokenFile = folder.newFile(fileName); - String token = UUID.randomUUID().toString(); - FileUtils.writeStringToFile(tokenFile, token); - options.set(RESTCatalogOptions.TOKEN_PROVIDER_PATH, tokenFile.getPath()); - BearTokenFileCredentialsProvider credentialsProvider = - (BearTokenFileCredentialsProvider) - CredentialsProviderFactory.createCredentialsProvider( - options, this.getClass().getClassLoader()); - assertEquals(token, credentialsProvider.token()); - } - - @Test - public void testCreateBearTokenFileCredentialsProviderFail() throws Exception { - Options options = new Options(); - options.set(CREDENTIALS_PROVIDER, CredentialsProviderType.BEAR_TOKEN_FILE.name()); - assertThrows( - IllegalArgumentException.class, - () -> - CredentialsProviderFactory.createCredentialsProvider( - options, this.getClass().getClassLoader())); - } - - @Test - public void testCreateRefreshBearTokenFileCredentialsProviderSuccess() throws Exception { - Options options = new Options(); - String fileName = "token"; - File tokenFile = folder.newFile(fileName); - String token = UUID.randomUUID().toString(); - FileUtils.writeStringToFile(tokenFile, token); - options.set(RESTCatalogOptions.TOKEN_PROVIDER_PATH, tokenFile.getPath()); - options.set(RESTCatalogOptions.TOKEN_EXPIRATION_TIME, Duration.ofSeconds(10L)); - BearTokenFileCredentialsProvider credentialsProvider = - (BearTokenFileCredentialsProvider) - CredentialsProviderFactory.createCredentialsProvider( - options, this.getClass().getClassLoader()); - assertEquals(token, credentialsProvider.token()); - } - - @Test - public void getCredentialsProviderTypeByConfWhenDefineTokenPath() { - Options options = new Options(); - options.set(RESTCatalogOptions.TOKEN_PROVIDER_PATH, "/a/b/c"); - assertEquals( - CredentialsProviderType.BEAR_TOKEN_FILE, - CredentialsProviderFactory.getCredentialsProviderTypeByConf(options)); - } - - @Test - public void getCredentialsProviderTypeByConfWhenConfNotDefined() { - Options options = new Options(); - assertEquals( - CredentialsProviderType.BEAR_TOKEN, - CredentialsProviderFactory.getCredentialsProviderTypeByConf(options)); - } - - @Test - public void getCredentialsProviderTypeByConfWhenDefineProviderType() { - Options options = new Options(); - options.set(CREDENTIALS_PROVIDER, CredentialsProviderType.BEAR_TOKEN_FILE.name()); - assertEquals( - CredentialsProviderType.BEAR_TOKEN_FILE, - CredentialsProviderFactory.getCredentialsProviderTypeByConf(options)); - } -}