From ce9c32a85acdc8dc714ffb00b5cf7d71fc30eb7d Mon Sep 17 00:00:00 2001 From: Alexandre Dutra Date: Fri, 19 Sep 2025 19:26:34 +0200 Subject: [PATCH] Auth: reorganize internal authentication components This PR contains no functional and no user-facing change. It is merely a refactor to better organize auth code. Summary of changes: - Moved all internal authentication components to the `org.apache.polaris.service.auth.internal` package and subpackages - Reduced visibility of utility classes - Renamed `TokenBroker` class hierarchy to stick to the naming standard: `JWTBroker` - Introduced `@PolarisImmutable` whenever appropriate - Removed unused `NoneTokenBrokerFactory` (we already have `DisabledOAuth2ApiService`) - Removed unused `TokenBrokerFactoryConfig` --- .../AuthenticationRealmConfiguration.java | 15 ++-- .../service/auth/NoneTokenBrokerFactory.java | 76 ------------------- .../service/auth/OAuthTokenErrorResponse.java | 72 ------------------ .../polaris/service/auth/TokenResponse.java | 59 -------------- .../InternalAuthenticationMechanism.java | 2 +- .../broker}/InternalPolarisToken.java | 3 +- .../auth/{ => internal/broker}/JWTBroker.java | 53 +++++++++---- .../{ => internal/broker}/KeyProvider.java | 8 +- .../broker}/LocalRSAKeyProvider.java | 37 +-------- .../auth/{ => internal/broker}/PemUtils.java | 18 ++--- .../broker/RSAKeyPairJWTBroker.java} | 8 +- .../broker/RSAKeyPairJWTBrokerFactory.java} | 15 ++-- .../broker/SymmetricKeyJWTBroker.java} | 6 +- .../broker/SymmetricKeyJWTBrokerFactory.java} | 14 ++-- .../{ => internal/broker}/TokenBroker.java | 38 +--------- .../broker}/TokenBrokerFactory.java | 2 +- .../broker}/TokenRequestValidator.java | 30 ++++---- .../auth/internal/broker/TokenResponse.java | 54 +++++++++++++ .../service}/DefaultOAuth2ApiService.java | 20 ++--- .../service}/DisabledOAuth2ApiService.java | 2 +- .../service/OAuthError.java} | 30 ++++---- .../service/OAuthTokenErrorResponse.java | 45 +++++++++++ .../{ => internal/service}/OAuthUtils.java | 21 +++-- .../service/config/ServiceProducers.java | 4 +- .../InternalAuthenticationMechanismTest.java | 2 +- .../broker}/JWTSymmetricKeyGeneratorTest.java | 4 +- .../broker}/LocalRSAKeyProviderTest.java | 10 +-- .../{ => internal/broker}/PemUtilsTest.java | 2 +- .../broker/RSAKeyPairJWTBrokerTest.java} | 13 ++-- .../broker}/TokenRequestValidatorTest.java | 17 +++-- .../{ => internal/broker}/TokenUtils.java | 2 +- .../service}/DefaultOAuth2ApiServiceTest.java | 50 ++++++------ .../test/PolarisIntegrationTestFixture.java | 2 +- 33 files changed, 298 insertions(+), 436 deletions(-) delete mode 100644 runtime/service/src/main/java/org/apache/polaris/service/auth/NoneTokenBrokerFactory.java delete mode 100644 runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthTokenErrorResponse.java delete mode 100644 runtime/service/src/main/java/org/apache/polaris/service/auth/TokenResponse.java rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/InternalPolarisToken.java (95%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/JWTBroker.java (77%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/KeyProvider.java (86%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/LocalRSAKeyProvider.java (70%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/PemUtils.java (90%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{JWTRSAKeyPair.java => internal/broker/RSAKeyPairJWTBroker.java} (86%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{JWTRSAKeyPairFactory.java => internal/broker/RSAKeyPairJWTBrokerFactory.java} (83%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{JWTSymmetricKeyBroker.java => internal/broker/SymmetricKeyJWTBroker.java} (90%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{JWTSymmetricKeyFactory.java => internal/broker/SymmetricKeyJWTBrokerFactory.java} (87%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/TokenBroker.java (56%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/TokenBrokerFactory.java (95%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/broker}/TokenRequestValidator.java (70%) create mode 100644 runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenResponse.java rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/service}/DefaultOAuth2ApiService.java (85%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/service}/DisabledOAuth2ApiService.java (95%) rename runtime/service/src/main/java/org/apache/polaris/service/auth/{TokenBrokerFactoryConfig.java => internal/service/OAuthError.java} (59%) create mode 100644 runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthTokenErrorResponse.java rename runtime/service/src/main/java/org/apache/polaris/service/auth/{ => internal/service}/OAuthUtils.java (65%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{ => internal/broker}/JWTSymmetricKeyGeneratorTest.java (96%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{ => internal/broker}/LocalRSAKeyProviderTest.java (92%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{ => internal/broker}/PemUtilsTest.java (98%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{JWTRSAKeyPairTest.java => internal/broker/RSAKeyPairJWTBrokerTest.java} (91%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{ => internal/broker}/TokenRequestValidatorTest.java (86%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{ => internal/broker}/TokenUtils.java (97%) rename runtime/service/src/test/java/org/apache/polaris/service/auth/{ => internal/service}/DefaultOAuth2ApiServiceTest.java (86%) diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java index 0db938b438..278dcbecd0 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/AuthenticationRealmConfiguration.java @@ -22,6 +22,8 @@ import java.nio.file.Path; import java.time.Duration; import java.util.Optional; +import org.apache.polaris.service.auth.internal.broker.TokenBrokerFactory; +import org.apache.polaris.service.catalog.api.IcebergRestOAuth2ApiService; public interface AuthenticationRealmConfiguration { @@ -38,10 +40,7 @@ public interface AuthenticationRealmConfiguration { interface AuthenticatorConfiguration { - /** - * The type of the identity provider. Must be a registered {@link - * org.apache.polaris.service.auth.Authenticator} identifier. - */ + /** The type of the identity provider. Must be a registered {@link Authenticator} identifier. */ @WithDefault("default") String type(); } @@ -54,8 +53,8 @@ interface AuthenticatorConfiguration { interface TokenServiceConfiguration { /** - * The type of the OAuth2 service. Must be a registered {@link - * org.apache.polaris.service.catalog.api.IcebergRestOAuth2ApiService} identifier. + * The type of the OAuth2 service. Must be a registered {@link IcebergRestOAuth2ApiService} + * identifier. */ @WithDefault("default") String type(); @@ -75,8 +74,8 @@ interface TokenBrokerConfiguration { Duration maxTokenGeneration(); /** - * The type of the token broker factory. Must be a registered {@link - * org.apache.polaris.service.auth.TokenBrokerFactory} identifier. + * The type of the token broker factory. Must be a registered {@link TokenBrokerFactory} + * identifier. */ @WithDefault("rsa-key-pair") String type(); diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/NoneTokenBrokerFactory.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/NoneTokenBrokerFactory.java deleted file mode 100644 index 4a94908abd..0000000000 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/NoneTokenBrokerFactory.java +++ /dev/null @@ -1,76 +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.polaris.service.auth; - -import io.smallrye.common.annotation.Identifier; -import jakarta.enterprise.context.ApplicationScoped; -import org.apache.polaris.core.PolarisCallContext; -import org.apache.polaris.core.context.RealmContext; -import org.apache.polaris.service.types.TokenType; - -/** Default {@link TokenBrokerFactory} that produces token brokers that do not do anything. */ -@ApplicationScoped -@Identifier("none") -public class NoneTokenBrokerFactory implements TokenBrokerFactory { - - public static final TokenBroker NONE_TOKEN_BROKER = - new TokenBroker() { - @Override - public boolean supportsGrantType(String grantType) { - return false; - } - - @Override - public boolean supportsRequestedTokenType(TokenType tokenType) { - return false; - } - - @Override - public TokenResponse generateFromClientSecrets( - String clientId, - String clientSecret, - String grantType, - String scope, - PolarisCallContext polarisCallContext, - TokenType requestedTokenType) { - return null; - } - - @Override - public TokenResponse generateFromToken( - TokenType subjectTokenType, - String subjectToken, - String grantType, - String scope, - PolarisCallContext polarisCallContext, - TokenType requestedTokenType) { - return null; - } - - @Override - public PolarisCredential verify(String token) { - return null; - } - }; - - @Override - public TokenBroker apply(RealmContext realmContext) { - return NONE_TOKEN_BROKER; - } -} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthTokenErrorResponse.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthTokenErrorResponse.java deleted file mode 100644 index 086c944821..0000000000 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthTokenErrorResponse.java +++ /dev/null @@ -1,72 +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.polaris.service.auth; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** An OAuth Error Token Response as defined by the Iceberg REST API OpenAPI Spec. */ -public class OAuthTokenErrorResponse { - - @SuppressWarnings("JavaLangClash") - public enum Error { - invalid_request("The request is invalid"), - invalid_client("The Client is invalid"), - invalid_grant("The grant is invalid"), - unauthorized_client("The client is not authorized"), - unsupported_grant_type("The grant type is invalid"), - invalid_scope("The scope is invalid"), - ; - - final String errorDescription; - - Error(String errorDescription) { - this.errorDescription = errorDescription; - } - - public String getErrorDescription() { - return errorDescription; - } - } - - private final String error; - private final String errorDescription; - private final String errorUri; - - /** Initlaizes a response from one of the supported errors */ - public OAuthTokenErrorResponse(Error error) { - this.error = error.name(); - this.errorDescription = error.getErrorDescription(); - this.errorUri = null; // Not yet used - } - - @JsonProperty("error") - public String getError() { - return error; - } - - @JsonProperty("error_description") - public String getErrorDescription() { - return errorDescription; - } - - @JsonProperty("error_uri") - public String getErrorUri() { - return errorUri; - } -} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenResponse.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenResponse.java deleted file mode 100644 index 84d0310a2d..0000000000 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenResponse.java +++ /dev/null @@ -1,59 +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.polaris.service.auth; - -import java.util.Optional; - -public class TokenResponse { - private final Optional error; - private String accessToken; - private String tokenType; - private Integer expiresIn; - - public TokenResponse(OAuthTokenErrorResponse.Error error) { - this.error = Optional.of(error); - } - - public TokenResponse(String accessToken, String tokenType, int expiresIn) { - this.accessToken = accessToken; - this.expiresIn = expiresIn; - this.tokenType = tokenType; - this.error = Optional.empty(); - } - - public boolean isValid() { - return error.isEmpty(); - } - - public OAuthTokenErrorResponse.Error getError() { - return error.get(); - } - - public String getAccessToken() { - return accessToken; - } - - public int getExpiresIn() { - return expiresIn; - } - - public String getTokenType() { - return tokenType; - } -} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanism.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanism.java index 657c4810a2..51d910d6da 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanism.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanism.java @@ -39,7 +39,7 @@ import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationType; import org.apache.polaris.service.auth.PolarisCredential; -import org.apache.polaris.service.auth.TokenBroker; +import org.apache.polaris.service.auth.internal.broker.TokenBroker; /** * A custom {@link HttpAuthenticationMechanism} that handles internal token authentication, that is, diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/InternalPolarisToken.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/InternalPolarisToken.java similarity index 95% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/InternalPolarisToken.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/InternalPolarisToken.java index 00586db872..8047d33180 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/InternalPolarisToken.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/InternalPolarisToken.java @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import com.google.common.base.Splitter; import jakarta.annotation.Nonnull; import java.util.Set; import java.util.stream.Collectors; import org.apache.polaris.immutables.PolarisImmutable; +import org.apache.polaris.service.auth.PolarisCredential; import org.immutables.value.Value; /** diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTBroker.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/JWTBroker.java similarity index 77% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/JWTBroker.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/JWTBroker.java index a5e845d74b..1cb13f5aac 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTBroker.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/JWTBroker.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; @@ -32,7 +32,10 @@ import org.apache.polaris.core.entity.PrincipalEntity; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.dao.entity.EntityResult; -import org.apache.polaris.service.auth.OAuthTokenErrorResponse.Error; +import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; +import org.apache.polaris.service.auth.DefaultAuthenticator; +import org.apache.polaris.service.auth.PolarisCredential; +import org.apache.polaris.service.auth.internal.service.OAuthError; import org.apache.polaris.service.types.TokenType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -88,27 +91,27 @@ public TokenResponse generateFromToken( PolarisCallContext polarisCallContext, TokenType requestedTokenType) { if (requestedTokenType != null && !TokenType.ACCESS_TOKEN.equals(requestedTokenType)) { - return new TokenResponse(OAuthTokenErrorResponse.Error.invalid_request); + return TokenResponse.of(OAuthError.invalid_request); } if (!TokenType.ACCESS_TOKEN.equals(subjectTokenType)) { - return new TokenResponse(OAuthTokenErrorResponse.Error.invalid_request); + return TokenResponse.of(OAuthError.invalid_request); } if (subjectToken == null || subjectToken.isBlank()) { - return new TokenResponse(OAuthTokenErrorResponse.Error.invalid_request); + return TokenResponse.of(OAuthError.invalid_request); } InternalPolarisToken decodedToken; try { decodedToken = verifyInternal(subjectToken); } catch (NotAuthorizedException e) { LOGGER.error("Failed to verify the token", e.getCause()); - return new TokenResponse(Error.invalid_client); + return TokenResponse.of(OAuthError.invalid_client); } EntityResult principalLookup = metaStoreManager.loadEntity( polarisCallContext, 0L, decodedToken.getPrincipalId(), PolarisEntityType.PRINCIPAL); if (!principalLookup.isSuccess() || principalLookup.getEntity().getType() != PolarisEntityType.PRINCIPAL) { - return new TokenResponse(OAuthTokenErrorResponse.Error.unauthorized_client); + return TokenResponse.of(OAuthError.unauthorized_client); } String tokenString = generateTokenString( @@ -116,7 +119,7 @@ public TokenResponse generateFromToken( decodedToken.getPrincipalId(), decodedToken.getClientId(), decodedToken.getScope()); - return new TokenResponse( + return TokenResponse.of( tokenString, TokenType.ACCESS_TOKEN.getValue(), maxTokenGenerationInSeconds); } @@ -130,21 +133,20 @@ public TokenResponse generateFromClientSecrets( TokenType requestedTokenType) { // Initial sanity checks TokenRequestValidator validator = new TokenRequestValidator(); - Optional initialValidationResponse = + Optional initialValidationResponse = validator.validateForClientCredentialsFlow(clientId, clientSecret, grantType, scope); if (initialValidationResponse.isPresent()) { - return new TokenResponse(initialValidationResponse.get()); + return TokenResponse.of(initialValidationResponse.get()); } Optional principal = - TokenBroker.findPrincipalEntity( - metaStoreManager, clientId, clientSecret, polarisCallContext); + findPrincipalEntity(clientId, clientSecret, polarisCallContext); if (principal.isEmpty()) { - return new TokenResponse(OAuthTokenErrorResponse.Error.unauthorized_client); + return TokenResponse.of(OAuthError.unauthorized_client); } String tokenString = generateTokenString(principal.get().getName(), principal.get().getId(), clientId, scope); - return new TokenResponse( + return TokenResponse.of( tokenString, TokenType.ACCESS_TOKEN.getValue(), maxTokenGenerationInSeconds); } @@ -177,4 +179,27 @@ public boolean supportsRequestedTokenType(TokenType tokenType) { private String scopes(String scope) { return scope == null || scope.isBlank() ? DefaultAuthenticator.PRINCIPAL_ROLE_ALL : scope; } + + private Optional findPrincipalEntity( + String clientId, String clientSecret, PolarisCallContext polarisCallContext) { + // Validate the principal is present and secrets match + PrincipalSecretsResult principalSecrets = + metaStoreManager.loadPrincipalSecrets(polarisCallContext, clientId); + if (!principalSecrets.isSuccess()) { + return Optional.empty(); + } + if (!principalSecrets.getPrincipalSecrets().matchesSecret(clientSecret)) { + return Optional.empty(); + } + EntityResult result = + metaStoreManager.loadEntity( + polarisCallContext, + 0L, + principalSecrets.getPrincipalSecrets().getPrincipalId(), + PolarisEntityType.PRINCIPAL); + if (!result.isSuccess() || result.getEntity().getType() != PolarisEntityType.PRINCIPAL) { + return Optional.empty(); + } + return Optional.of(PrincipalEntity.of(result.getEntity())); + } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/KeyProvider.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/KeyProvider.java similarity index 86% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/KeyProvider.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/KeyProvider.java index a28b9a0597..51dd1bd1a4 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/KeyProvider.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/KeyProvider.java @@ -16,13 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import java.security.PrivateKey; import java.security.PublicKey; -public interface KeyProvider { - PublicKey getPublicKey(); +interface KeyProvider { + PublicKey publicKey(); - PrivateKey getPrivateKey(); + PrivateKey privateKey(); } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/LocalRSAKeyProvider.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/LocalRSAKeyProvider.java similarity index 70% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/LocalRSAKeyProvider.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/LocalRSAKeyProvider.java index d9fba39ac8..cd8e8b4f8f 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/LocalRSAKeyProvider.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/LocalRSAKeyProvider.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import jakarta.annotation.Nonnull; import java.io.IOException; @@ -28,24 +28,15 @@ import org.slf4j.LoggerFactory; /** Holds a public / private key pair in memory. */ -public class LocalRSAKeyProvider implements KeyProvider { +record LocalRSAKeyProvider(PublicKey publicKey, PrivateKey privateKey) implements KeyProvider { private static final Logger LOGGER = LoggerFactory.getLogger(LocalRSAKeyProvider.class); - private final PublicKey publicKey; - private final PrivateKey privateKey; - - public LocalRSAKeyProvider(@Nonnull KeyPair keyPair) { + LocalRSAKeyProvider(@Nonnull KeyPair keyPair) { this(keyPair.getPublic(), keyPair.getPrivate()); } - public LocalRSAKeyProvider(@Nonnull PublicKey publicKey, @Nonnull PrivateKey privateKey) { - this.publicKey = publicKey; - this.privateKey = privateKey; - } - - public static LocalRSAKeyProvider fromFiles( - @Nonnull Path publicKeyFile, @Nonnull Path privateKeyFile) { + static LocalRSAKeyProvider fromFiles(@Nonnull Path publicKeyFile, @Nonnull Path privateKeyFile) { return new LocalRSAKeyProvider( readPublicKeyFile(publicKeyFile), readPrivateKeyFile(privateKeyFile)); } @@ -68,24 +59,4 @@ private static PublicKey readPublicKeyFile(Path publicKeyFileLocation) { throw new RuntimeException("Unable to read public key from file " + publicKeyFileLocation, e); } } - - /** - * Getter for the Public Key instance - * - * @return the Public Key instance - */ - @Override - public PublicKey getPublicKey() { - return publicKey; - } - - /** - * Getter for the Private Key instance. Used to sign the content on the JWT signing stage. - * - * @return the Private Key instance - */ - @Override - public PrivateKey getPrivateKey() { - return privateKey; - } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/PemUtils.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/PemUtils.java similarity index 90% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/PemUtils.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/PemUtils.java index c939424014..70994020a7 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/PemUtils.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/PemUtils.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static java.nio.charset.StandardCharsets.UTF_8; @@ -38,7 +38,7 @@ import java.security.spec.X509EncodedKeySpec; import java.util.Base64; -public class PemUtils { +final class PemUtils { private static byte[] parsePEMFile(Path pemPath) throws IOException { if (!Files.isRegularFile(pemPath) || !Files.exists(pemPath)) { @@ -108,31 +108,29 @@ private static PrivateKey getPrivateKey(byte[] keyBytes, String algorithm) { } } - public static PublicKey readPublicKeyFromFile(Path filepath, String algorithm) - throws IOException { + static PublicKey readPublicKeyFromFile(Path filepath, String algorithm) throws IOException { byte[] bytes = PemUtils.parsePEMFile(filepath); return PemUtils.getPublicKey(bytes, algorithm); } - public static PrivateKey readPrivateKeyFromFile(Path filepath, String algorithm) - throws IOException { + static PrivateKey readPrivateKeyFromFile(Path filepath, String algorithm) throws IOException { byte[] bytes = PemUtils.parsePEMFile(filepath); return PemUtils.getPrivateKey(bytes, algorithm); } - public static KeyPair generateKeyPair() throws NoSuchAlgorithmException { + static KeyPair generateKeyPair() throws NoSuchAlgorithmException { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); kpg.initialize(2048); return kpg.generateKeyPair(); } - public static void generateKeyPairFiles(Path privateFileLocation, Path publicFileLocation) + static void generateKeyPairFiles(Path privateFileLocation, Path publicFileLocation) throws NoSuchAlgorithmException, IOException { writeKeyPairFiles(generateKeyPair(), privateFileLocation, publicFileLocation); } - public static void writeKeyPairFiles( - KeyPair keyPair, Path privateFileLocation, Path publicFileLocation) throws IOException { + static void writeKeyPairFiles(KeyPair keyPair, Path privateFileLocation, Path publicFileLocation) + throws IOException { try (BufferedWriter writer = Files.newBufferedWriter(privateFileLocation, UTF_8)) { writer.write("-----BEGIN PRIVATE KEY-----"); writer.newLine(); diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPair.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBroker.java similarity index 86% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPair.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBroker.java index 1c637d3a49..a2d903f6e7 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPair.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBroker.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import com.auth0.jwt.algorithms.Algorithm; import java.security.interfaces.RSAPrivateKey; @@ -24,11 +24,11 @@ import org.apache.polaris.core.persistence.PolarisMetaStoreManager; /** Generates a JWT using a Public/Private RSA Key */ -public class JWTRSAKeyPair extends JWTBroker { +public class RSAKeyPairJWTBroker extends JWTBroker { private final KeyProvider keyProvider; - public JWTRSAKeyPair( + RSAKeyPairJWTBroker( PolarisMetaStoreManager metaStoreManager, int maxTokenGenerationInSeconds, KeyProvider keyProvider) { @@ -39,6 +39,6 @@ public JWTRSAKeyPair( @Override public Algorithm getAlgorithm() { return Algorithm.RSA256( - (RSAPublicKey) keyProvider.getPublicKey(), (RSAPrivateKey) keyProvider.getPrivateKey()); + (RSAPublicKey) keyProvider.publicKey(), (RSAPrivateKey) keyProvider.privateKey()); } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPairFactory.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBrokerFactory.java similarity index 83% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPairFactory.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBrokerFactory.java index 4fe1fde0d5..74b4f90ef8 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTRSAKeyPairFactory.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBrokerFactory.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import io.smallrye.common.annotation.Identifier; import jakarta.enterprise.context.ApplicationScoped; @@ -28,19 +28,21 @@ import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.persistence.MetaStoreManagerFactory; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; +import org.apache.polaris.service.auth.AuthenticationConfiguration; +import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationRealmConfiguration.TokenBrokerConfiguration.RSAKeyPairConfiguration; @ApplicationScoped @Identifier("rsa-key-pair") -public class JWTRSAKeyPairFactory implements TokenBrokerFactory { +public class RSAKeyPairJWTBrokerFactory implements TokenBrokerFactory { private final MetaStoreManagerFactory metaStoreManagerFactory; private final AuthenticationConfiguration authenticationConfiguration; - private final ConcurrentMap tokenBrokers = new ConcurrentHashMap<>(); + private final ConcurrentMap tokenBrokers = new ConcurrentHashMap<>(); @Inject - public JWTRSAKeyPairFactory( + public RSAKeyPairJWTBrokerFactory( MetaStoreManagerFactory metaStoreManagerFactory, AuthenticationConfiguration authenticationConfiguration) { this.metaStoreManagerFactory = metaStoreManagerFactory; @@ -53,7 +55,7 @@ public TokenBroker apply(RealmContext realmContext) { realmContext.getRealmIdentifier(), k -> createTokenBroker(realmContext)); } - private JWTRSAKeyPair createTokenBroker(RealmContext realmContext) { + private RSAKeyPairJWTBroker createTokenBroker(RealmContext realmContext) { AuthenticationRealmConfiguration config = authenticationConfiguration.forRealm(realmContext); Duration maxTokenGeneration = config.tokenBroker().maxTokenGeneration(); KeyProvider keyProvider = @@ -64,7 +66,8 @@ private JWTRSAKeyPair createTokenBroker(RealmContext realmContext) { .orElseGet(this::generateEphemeralKeyPair); PolarisMetaStoreManager metaStoreManager = metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext); - return new JWTRSAKeyPair(metaStoreManager, (int) maxTokenGeneration.toSeconds(), keyProvider); + return new RSAKeyPairJWTBroker( + metaStoreManager, (int) maxTokenGeneration.toSeconds(), keyProvider); } private KeyProvider fileSystemKeyPair(RSAKeyPairConfiguration config) { diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyBroker.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/SymmetricKeyJWTBroker.java similarity index 90% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyBroker.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/SymmetricKeyJWTBroker.java index 16c9e15511..0ca456f264 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyBroker.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/SymmetricKeyJWTBroker.java @@ -16,17 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import com.auth0.jwt.algorithms.Algorithm; import java.util.function.Supplier; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; /** Generates a JWT using a Symmetric Key. */ -public class JWTSymmetricKeyBroker extends JWTBroker { +public class SymmetricKeyJWTBroker extends JWTBroker { private final Supplier secretSupplier; - public JWTSymmetricKeyBroker( + public SymmetricKeyJWTBroker( PolarisMetaStoreManager metaStoreManager, int maxTokenGenerationInSeconds, Supplier secretSupplier) { diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyFactory.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/SymmetricKeyJWTBrokerFactory.java similarity index 87% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyFactory.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/SymmetricKeyJWTBrokerFactory.java index ed33800646..302b32393f 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/JWTSymmetricKeyFactory.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/SymmetricKeyJWTBrokerFactory.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static com.google.common.base.Preconditions.checkState; @@ -32,20 +32,22 @@ import java.util.function.Supplier; import org.apache.polaris.core.context.RealmContext; import org.apache.polaris.core.persistence.MetaStoreManagerFactory; +import org.apache.polaris.service.auth.AuthenticationConfiguration; +import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationRealmConfiguration.TokenBrokerConfiguration.SymmetricKeyConfiguration; @ApplicationScoped @Identifier("symmetric-key") -public class JWTSymmetricKeyFactory implements TokenBrokerFactory { +public class SymmetricKeyJWTBrokerFactory implements TokenBrokerFactory { private final MetaStoreManagerFactory metaStoreManagerFactory; private final AuthenticationConfiguration authenticationConfiguration; - private final ConcurrentMap tokenBrokers = + private final ConcurrentMap tokenBrokers = new ConcurrentHashMap<>(); @Inject - public JWTSymmetricKeyFactory( + public SymmetricKeyJWTBrokerFactory( MetaStoreManagerFactory metaStoreManagerFactory, AuthenticationConfiguration authenticationConfiguration) { this.metaStoreManagerFactory = metaStoreManagerFactory; @@ -58,7 +60,7 @@ public TokenBroker apply(RealmContext realmContext) { realmContext.getRealmIdentifier(), k -> createTokenBroker(realmContext)); } - private JWTSymmetricKeyBroker createTokenBroker(RealmContext realmContext) { + private SymmetricKeyJWTBroker createTokenBroker(RealmContext realmContext) { AuthenticationRealmConfiguration config = authenticationConfiguration.forRealm(realmContext); Duration maxTokenGeneration = config.tokenBroker().maxTokenGeneration(); SymmetricKeyConfiguration symmetricKeyConfiguration = @@ -70,7 +72,7 @@ private JWTSymmetricKeyBroker createTokenBroker(RealmContext realmContext) { Path file = symmetricKeyConfiguration.file().orElse(null); checkState(secret != null || file != null, "Either file or secret must be set"); Supplier secretSupplier = secret != null ? () -> secret : readSecretFromDisk(file); - return new JWTSymmetricKeyBroker( + return new SymmetricKeyJWTBroker( metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext), (int) maxTokenGeneration.toSeconds(), secretSupplier); diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenBroker.java similarity index 56% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenBroker.java index e424dff1aa..e35561b073 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBroker.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenBroker.java @@ -16,19 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; -import jakarta.annotation.Nonnull; -import java.util.Optional; import org.apache.polaris.core.PolarisCallContext; -import org.apache.polaris.core.entity.PolarisEntityType; -import org.apache.polaris.core.entity.PrincipalEntity; -import org.apache.polaris.core.persistence.PolarisMetaStoreManager; -import org.apache.polaris.core.persistence.dao.entity.EntityResult; -import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult; +import org.apache.polaris.service.auth.PolarisCredential; import org.apache.polaris.service.types.TokenType; -/** Generic token class intended to be extended by different token types */ +/** A broker for generating and verifying tokens. */ public interface TokenBroker { boolean supportsGrantType(String grantType); @@ -63,30 +57,4 @@ TokenResponse generateFromToken( /** Decodes and verifies the token, then returns the associated {@link PolarisCredential}. */ PolarisCredential verify(String token); - - static @Nonnull Optional findPrincipalEntity( - PolarisMetaStoreManager metaStoreManager, - String clientId, - String clientSecret, - PolarisCallContext polarisCallContext) { - // Validate the principal is present and secrets match - PrincipalSecretsResult principalSecrets = - metaStoreManager.loadPrincipalSecrets(polarisCallContext, clientId); - if (!principalSecrets.isSuccess()) { - return Optional.empty(); - } - if (!principalSecrets.getPrincipalSecrets().matchesSecret(clientSecret)) { - return Optional.empty(); - } - EntityResult result = - metaStoreManager.loadEntity( - polarisCallContext, - 0L, - principalSecrets.getPrincipalSecrets().getPrincipalId(), - PolarisEntityType.PRINCIPAL); - if (!result.isSuccess() || result.getEntity().getType() != PolarisEntityType.PRINCIPAL) { - return Optional.empty(); - } - return Optional.of(PrincipalEntity.of(result.getEntity())); - } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBrokerFactory.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenBrokerFactory.java similarity index 95% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBrokerFactory.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenBrokerFactory.java index 131f3ed64f..52d8aa1b72 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBrokerFactory.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenBrokerFactory.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import java.util.function.Function; import org.apache.polaris.core.context.RealmContext; diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenRequestValidator.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenRequestValidator.java similarity index 70% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/TokenRequestValidator.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenRequestValidator.java index b123620cbb..07b3c18bc0 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenRequestValidator.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenRequestValidator.java @@ -16,22 +16,24 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; +import org.apache.polaris.service.auth.internal.service.OAuthError; -public class TokenRequestValidator { +final class TokenRequestValidator { static final Logger LOGGER = Logger.getLogger(TokenRequestValidator.class.getName()); - public static final String TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"; - public static final String CLIENT_CREDENTIALS = "client_credentials"; - public static final Set ALLOWED_GRANT_TYPES = Set.of(CLIENT_CREDENTIALS, TOKEN_EXCHANGE); + static final String TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"; + static final String CLIENT_CREDENTIALS = "client_credentials"; + static final Set ALLOWED_GRANT_TYPES = Set.of(CLIENT_CREDENTIALS, TOKEN_EXCHANGE); + static final String POLARIS_ROLE_PREFIX = "PRINCIPAL_ROLE:"; /** Default constructor */ - public TokenRequestValidator() {} + TokenRequestValidator() {} /** * Validates the incoming Client Credentials flow. @@ -44,7 +46,7 @@ public TokenRequestValidator() {} * @param scope while optional in the Iceberg REST API Spec we make it required and expect it to * conform to the format "PRINCIPAL_ROLE:NAME PRINCIPAL_ROLE:NAME2 ..." */ - public Optional validateForClientCredentialsFlow( + Optional validateForClientCredentialsFlow( final String clientId, final String clientSecret, final String grantType, @@ -52,25 +54,25 @@ public Optional validateForClientCredentialsFlow( if (clientId == null || clientId.isEmpty() || clientSecret == null || clientSecret.isEmpty()) { // TODO: Figure out how to get the authorization header from `securityContext` LOGGER.info("Missing Client ID or Client Secret in Request Body"); - return Optional.of(OAuthTokenErrorResponse.Error.invalid_client); + return Optional.of(OAuthError.invalid_client); } if (grantType == null || grantType.isEmpty() || !ALLOWED_GRANT_TYPES.contains(grantType)) { LOGGER.info("Invalid grant type: " + grantType); - return Optional.of(OAuthTokenErrorResponse.Error.invalid_grant); + return Optional.of(OAuthError.invalid_grant); } if (scope == null || scope.isEmpty()) { LOGGER.info("Missing scope in Request Body"); - return Optional.of(OAuthTokenErrorResponse.Error.invalid_scope); + return Optional.of(OAuthError.invalid_scope); } String[] scopes = scope.split(" "); for (String s : scopes) { - if (!s.startsWith(OAuthUtils.POLARIS_ROLE_PREFIX)) { + if (!s.startsWith(POLARIS_ROLE_PREFIX)) { LOGGER.info("Invalid scope provided. scopes=" + s + "scopes=" + scope); - return Optional.of(OAuthTokenErrorResponse.Error.invalid_scope); + return Optional.of(OAuthError.invalid_scope); } - if (s.replaceFirst(OAuthUtils.POLARIS_ROLE_PREFIX, "").isEmpty()) { + if (s.replaceFirst(POLARIS_ROLE_PREFIX, "").isEmpty()) { LOGGER.info("Invalid scope provided. scopes=" + s + "scopes=" + scope); - return Optional.of(OAuthTokenErrorResponse.Error.invalid_scope); + return Optional.of(OAuthError.invalid_scope); } } return Optional.empty(); diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenResponse.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenResponse.java new file mode 100644 index 0000000000..d96b4ad73b --- /dev/null +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/broker/TokenResponse.java @@ -0,0 +1,54 @@ +/* + * 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.polaris.service.auth.internal.broker; + +import jakarta.annotation.Nullable; +import org.apache.polaris.immutables.PolarisImmutable; +import org.apache.polaris.service.auth.internal.service.OAuthError; +import org.immutables.value.Value; + +@PolarisImmutable +public interface TokenResponse { + + static TokenResponse of(String accessToken, String tokenType, int expiresIn) { + return ImmutableTokenResponse.builder() + .accessToken(accessToken) + .tokenType(tokenType) + .expiresIn(expiresIn) + .build(); + } + + static TokenResponse of(OAuthError error) { + return ImmutableTokenResponse.builder().error(error).build(); + } + + @Nullable + OAuthError getError(); + + @Nullable + String getAccessToken(); + + @Value.Default + default int getExpiresIn() { + return 0; + } + + @Nullable + String getTokenType(); +} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultOAuth2ApiService.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/DefaultOAuth2ApiService.java similarity index 85% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultOAuth2ApiService.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/DefaultOAuth2ApiService.java index 66e8d056af..e02f938882 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/DefaultOAuth2ApiService.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/DefaultOAuth2ApiService.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.service; import static java.nio.charset.StandardCharsets.UTF_8; @@ -29,6 +29,8 @@ import org.apache.iceberg.rest.responses.OAuthTokenResponse; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.service.auth.internal.broker.TokenBroker; +import org.apache.polaris.service.auth.internal.broker.TokenResponse; import org.apache.polaris.service.catalog.api.IcebergRestOAuth2ApiService; import org.apache.polaris.service.types.TokenType; import org.slf4j.Logger; @@ -71,13 +73,13 @@ public Response getToken( SecurityContext securityContext) { if (!tokenBroker.supportsGrantType(grantType)) { - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.unsupported_grant_type); + return OAuthUtils.getResponseFromError(OAuthError.unsupported_grant_type); } if (!tokenBroker.supportsRequestedTokenType(requestedTokenType)) { - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.invalid_request); + return OAuthUtils.getResponseFromError(OAuthError.invalid_request); } if (authHeader == null && clientSecret == null) { - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.invalid_client); + return OAuthUtils.getResponseFromError(OAuthError.invalid_client); } // token exchange with client id and client secret in the authorization header means the client // has previously attempted to refresh an access token, but refreshing was not supported by the @@ -86,7 +88,7 @@ public Response getToken( String credentials = new String(Base64.getDecoder().decode(authHeader.substring(6).getBytes(UTF_8)), UTF_8); if (!credentials.contains(":")) { - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.invalid_request); + return OAuthUtils.getResponseFromError(OAuthError.invalid_request); } LOGGER.debug("Found credentials in auth header - treating as client_credentials"); String[] parts = credentials.split(":", 2); @@ -95,7 +97,7 @@ public Response getToken( clientSecret = parts[1]; } else { LOGGER.debug("Don't know how to parse Basic auth header"); - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.invalid_request); + return OAuthUtils.getResponseFromError(OAuthError.invalid_request); } } TokenResponse tokenResponse; @@ -118,12 +120,12 @@ public Response getToken( callContext.getPolarisCallContext(), requestedTokenType); } else { - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.invalid_request); + return OAuthUtils.getResponseFromError(OAuthError.invalid_request); } if (tokenResponse == null) { - return OAuthUtils.getResponseFromError(OAuthTokenErrorResponse.Error.unsupported_grant_type); + return OAuthUtils.getResponseFromError(OAuthError.unsupported_grant_type); } - if (!tokenResponse.isValid()) { + if (tokenResponse.getError() != null) { return OAuthUtils.getResponseFromError(tokenResponse.getError()); } return Response.ok( diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/DisabledOAuth2ApiService.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/DisabledOAuth2ApiService.java similarity index 95% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/DisabledOAuth2ApiService.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/DisabledOAuth2ApiService.java index 16877e0976..413c6304e5 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/DisabledOAuth2ApiService.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/DisabledOAuth2ApiService.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.service; import io.smallrye.common.annotation.Identifier; import jakarta.enterprise.context.ApplicationScoped; diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBrokerFactoryConfig.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthError.java similarity index 59% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBrokerFactoryConfig.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthError.java index 20811e140d..9d162fe219 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/TokenBrokerFactoryConfig.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthError.java @@ -16,21 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; -import jakarta.annotation.Nullable; +package org.apache.polaris.service.auth.internal.service; -/** - * This is a union of configuration settings of all token brokers. - * - * @see TokenBrokerFactory - */ -public interface TokenBrokerFactoryConfig { - @Nullable - String file(); +public enum OAuthError { + invalid_request("The request is invalid"), + invalid_client("The Client is invalid"), + invalid_grant("The grant is invalid"), + unauthorized_client("The client is not authorized"), + unsupported_grant_type("The grant type is invalid"), + invalid_scope("The scope is invalid"), + ; + + final String errorDescription; - @Nullable - String secret(); + OAuthError(String errorDescription) { + this.errorDescription = errorDescription; + } - int maxTokenGenerationInSeconds(); + public String getErrorDescription() { + return errorDescription; + } } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthTokenErrorResponse.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthTokenErrorResponse.java new file mode 100644 index 0000000000..3dcf4bbe4b --- /dev/null +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthTokenErrorResponse.java @@ -0,0 +1,45 @@ +/* + * 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.polaris.service.auth.internal.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.annotation.Nullable; +import org.apache.polaris.immutables.PolarisImmutable; + +/** An OAuth Error Token Response as defined by the Iceberg REST API OpenAPI Spec. */ +@PolarisImmutable +interface OAuthTokenErrorResponse { + + static OAuthTokenErrorResponse of(OAuthError error) { + return ImmutableOAuthTokenErrorResponse.builder() + .error(error.name()) + .errorDescription(error.getErrorDescription()) + .build(); + } + + @JsonProperty("error") + String getError(); + + @JsonProperty("error_description") + String getErrorDescription(); + + @Nullable // currently unused + @JsonProperty("error_uri") + String getErrorUri(); +} diff --git a/runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthUtils.java b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthUtils.java similarity index 65% rename from runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthUtils.java rename to runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthUtils.java index 2b3e928e98..1b2e05e9e8 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/auth/OAuthUtils.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/auth/internal/service/OAuthUtils.java @@ -16,41 +16,38 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.service; import jakarta.ws.rs.core.Response; /** Simple utility class to assist with OAuth operations */ -public class OAuthUtils { - public static final String POLARIS_ROLE_PREFIX = "PRINCIPAL_ROLE:"; +final class OAuthUtils { - public static Response getResponseFromError(OAuthTokenErrorResponse.Error error) { + static Response getResponseFromError(OAuthError error) { return switch (error) { case unauthorized_client -> Response.status(Response.Status.UNAUTHORIZED) - .entity( - new OAuthTokenErrorResponse(OAuthTokenErrorResponse.Error.unauthorized_client)) + .entity(OAuthTokenErrorResponse.of(OAuthError.unauthorized_client)) .build(); case invalid_client -> Response.status(Response.Status.BAD_REQUEST) - .entity(new OAuthTokenErrorResponse(OAuthTokenErrorResponse.Error.invalid_client)) + .entity(OAuthTokenErrorResponse.of(OAuthError.invalid_client)) .build(); case invalid_grant -> Response.status(Response.Status.BAD_REQUEST) - .entity(new OAuthTokenErrorResponse(OAuthTokenErrorResponse.Error.invalid_grant)) + .entity(OAuthTokenErrorResponse.of(OAuthError.invalid_grant)) .build(); case unsupported_grant_type -> Response.status(Response.Status.BAD_REQUEST) - .entity( - new OAuthTokenErrorResponse(OAuthTokenErrorResponse.Error.unsupported_grant_type)) + .entity(OAuthTokenErrorResponse.of(OAuthError.unsupported_grant_type)) .build(); case invalid_scope -> Response.status(Response.Status.BAD_REQUEST) - .entity(new OAuthTokenErrorResponse(OAuthTokenErrorResponse.Error.invalid_scope)) + .entity(OAuthTokenErrorResponse.of(OAuthError.invalid_scope)) .build(); default -> Response.status(Response.Status.BAD_REQUEST) - .entity(new OAuthTokenErrorResponse(OAuthTokenErrorResponse.Error.invalid_request)) + .entity(OAuthTokenErrorResponse.of(OAuthError.invalid_request)) .build(); }; } diff --git a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java index c833686b49..49a20a3d2d 100644 --- a/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java +++ b/runtime/service/src/main/java/org/apache/polaris/service/config/ServiceProducers.java @@ -60,9 +60,9 @@ import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationType; import org.apache.polaris.service.auth.Authenticator; -import org.apache.polaris.service.auth.TokenBroker; -import org.apache.polaris.service.auth.TokenBrokerFactory; import org.apache.polaris.service.auth.external.tenant.OidcTenantResolver; +import org.apache.polaris.service.auth.internal.broker.TokenBroker; +import org.apache.polaris.service.auth.internal.broker.TokenBrokerFactory; import org.apache.polaris.service.catalog.api.IcebergRestOAuth2ApiService; import org.apache.polaris.service.catalog.io.FileIOConfiguration; import org.apache.polaris.service.catalog.io.FileIOFactory; diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanismTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanismTest.java index a7c3308bdd..447e28e5c9 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanismTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/InternalAuthenticationMechanismTest.java @@ -35,7 +35,7 @@ import org.apache.polaris.service.auth.AuthenticationRealmConfiguration; import org.apache.polaris.service.auth.AuthenticationType; import org.apache.polaris.service.auth.PolarisCredential; -import org.apache.polaris.service.auth.TokenBroker; +import org.apache.polaris.service.auth.internal.broker.TokenBroker; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/JWTSymmetricKeyGeneratorTest.java similarity index 96% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/JWTSymmetricKeyGeneratorTest.java index 43efedb6ab..e3ff0518e3 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/JWTSymmetricKeyGeneratorTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/JWTSymmetricKeyGeneratorTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static org.assertj.core.api.Assertions.assertThat; @@ -60,7 +60,7 @@ public void testJWTSymmetricKeyGenerator() { Mockito.when( metastoreManager.loadEntity(polarisCallContext, 0L, 1L, PolarisEntityType.PRINCIPAL)) .thenReturn(new EntityResult(principal)); - TokenBroker generator = new JWTSymmetricKeyBroker(metastoreManager, 666, () -> "polaris"); + TokenBroker generator = new SymmetricKeyJWTBroker(metastoreManager, 666, () -> "polaris"); TokenResponse token = generator.generateFromClientSecrets( clientId, diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/LocalRSAKeyProviderTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/LocalRSAKeyProviderTest.java similarity index 92% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/LocalRSAKeyProviderTest.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/LocalRSAKeyProviderTest.java index e671d3d907..e0efda8785 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/LocalRSAKeyProviderTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/LocalRSAKeyProviderTest.java @@ -17,7 +17,7 @@ * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static org.assertj.core.api.InstanceOfAssertFactories.BYTE_ARRAY; @@ -46,11 +46,11 @@ public void fromFiles(@TempDir Path tempDir) throws Exception { var keyProvider = LocalRSAKeyProvider.fromFiles(publicKeyFile, privateKeyFile); soft.assertThat(keyProvider) - .extracting(KeyProvider::getPublicKey) + .extracting(KeyProvider::publicKey) .extracting(PublicKey::getEncoded, BYTE_ARRAY) .containsExactly(generatedPublicKey.getEncoded()); soft.assertThat(keyProvider) - .extracting(KeyProvider::getPrivateKey) + .extracting(KeyProvider::privateKey) .extracting(PrivateKey::getEncoded, BYTE_ARRAY) .containsExactly(generatedPrivateKey.getEncoded()); } @@ -64,11 +64,11 @@ public void onHeap() throws Exception { var keyProvider = new LocalRSAKeyProvider(keyPair); soft.assertThat(keyProvider) - .extracting(KeyProvider::getPublicKey) + .extracting(KeyProvider::publicKey) .extracting(PublicKey::getEncoded, BYTE_ARRAY) .containsExactly(generatedPublicKey.getEncoded()); soft.assertThat(keyProvider) - .extracting(KeyProvider::getPrivateKey) + .extracting(KeyProvider::privateKey) .extracting(PrivateKey::getEncoded, BYTE_ARRAY) .containsExactly(generatedPrivateKey.getEncoded()); } diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/PemUtilsTest.java similarity index 98% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/PemUtilsTest.java index 36e62ceae5..a3cdb39748 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/PemUtilsTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/PemUtilsTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBrokerTest.java similarity index 91% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBrokerTest.java index be42765980..3bc4ff6a66 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/JWTRSAKeyPairTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/RSAKeyPairJWTBrokerTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static org.assertj.core.api.Assertions.assertThat; @@ -42,7 +42,7 @@ import org.mockito.Mockito; @QuarkusTest -public class JWTRSAKeyPairTest { +public class RSAKeyPairJWTBrokerTest { @Inject protected PolarisConfigurationStore configurationStore; @@ -72,7 +72,7 @@ public void testSuccessfulTokenGeneration() throws Exception { metastoreManager.loadEntity(polarisCallContext, 0L, 1L, PolarisEntityType.PRINCIPAL)) .thenReturn(new EntityResult(principal)); KeyProvider provider = new LocalRSAKeyProvider(keyPair); - TokenBroker tokenBroker = new JWTRSAKeyPair(metastoreManager, 420, provider); + TokenBroker tokenBroker = new RSAKeyPairJWTBroker(metastoreManager, 420, provider); TokenResponse token = tokenBroker.generateFromClientSecrets( clientId, @@ -84,13 +84,12 @@ public void testSuccessfulTokenGeneration() throws Exception { assertThat(token).isNotNull(); assertThat(token.getExpiresIn()).isEqualTo(420); - assertThat(provider.getPrivateKey()).isNotNull(); - assertThat(provider.getPublicKey()).isNotNull(); + assertThat(provider.privateKey()).isNotNull(); + assertThat(provider.publicKey()).isNotNull(); JWTVerifier verifier = JWT.require( Algorithm.RSA256( - (RSAPublicKey) provider.getPublicKey(), - (RSAPrivateKey) provider.getPrivateKey())) + (RSAPublicKey) provider.publicKey(), (RSAPrivateKey) provider.privateKey())) .withIssuer("polaris") .build(); DecodedJWT decodedJWT = verifier.verify(token.getAccessToken()); diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/TokenRequestValidatorTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/TokenRequestValidatorTest.java similarity index 86% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/TokenRequestValidatorTest.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/TokenRequestValidatorTest.java index bd8dd9a3a6..e109869486 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/TokenRequestValidatorTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/TokenRequestValidatorTest.java @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; +import org.apache.polaris.service.auth.internal.service.OAuthError; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -30,12 +31,12 @@ public void testValidateForClientCredentialsFlowNullClientId() { new TokenRequestValidator() .validateForClientCredentialsFlow(null, "notnull", "notnull", "nontnull") .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_client); + .isEqualTo(OAuthError.invalid_client); Assertions.assertThat( new TokenRequestValidator() .validateForClientCredentialsFlow("", "notnull", "notnull", "nonnull") .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_client); + .isEqualTo(OAuthError.invalid_client); } @Test @@ -44,12 +45,12 @@ public void testValidateForClientCredentialsFlowNullClientSecret() { new TokenRequestValidator() .validateForClientCredentialsFlow("client-id", null, "notnull", "nontnull") .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_client); + .isEqualTo(OAuthError.invalid_client); Assertions.assertThat( new TokenRequestValidator() .validateForClientCredentialsFlow("client-id", "", "notnull", "notnull") .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_client); + .isEqualTo(OAuthError.invalid_client); } @Test @@ -59,12 +60,12 @@ public void testValidateForClientCredentialsFlowInvalidGrantType() { .validateForClientCredentialsFlow( "client-id", "client-secret", "not-client-credentials", "notnull") .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_grant); + .isEqualTo(OAuthError.invalid_grant); Assertions.assertThat( new TokenRequestValidator() .validateForClientCredentialsFlow("client-id", "client-secret", "grant", "notnull") .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_grant); + .isEqualTo(OAuthError.invalid_grant); } @ParameterizedTest @@ -75,7 +76,7 @@ public void testValidateForClientCredentialsFlowInvalidScope(String scope) { .validateForClientCredentialsFlow( "client-id", "client-secret", "client_credentials", scope) .get()) - .isEqualTo(OAuthTokenErrorResponse.Error.invalid_scope); + .isEqualTo(OAuthError.invalid_scope); } @Test diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/TokenUtils.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/TokenUtils.java similarity index 97% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/TokenUtils.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/TokenUtils.java index 604da26dd8..95b2b4db93 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/TokenUtils.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/broker/TokenUtils.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.broker; import static org.apache.polaris.service.auth.DefaultAuthenticator.PRINCIPAL_ROLE_ALL; import static org.apache.polaris.service.context.TestRealmContextResolver.REALM_PROPERTY_KEY; diff --git a/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultOAuth2ApiServiceTest.java b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/service/DefaultOAuth2ApiServiceTest.java similarity index 86% rename from runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultOAuth2ApiServiceTest.java rename to runtime/service/src/test/java/org/apache/polaris/service/auth/internal/service/DefaultOAuth2ApiServiceTest.java index e4f2f47a12..14bcc45bb3 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/auth/DefaultOAuth2ApiServiceTest.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/auth/internal/service/DefaultOAuth2ApiServiceTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.polaris.service.auth; +package org.apache.polaris.service.auth.internal.service; import static org.mockito.Mockito.when; @@ -27,6 +27,8 @@ import org.apache.polaris.core.PolarisCallContext; import org.apache.polaris.core.context.CallContext; import org.apache.polaris.core.context.RealmContext; +import org.apache.polaris.service.auth.internal.broker.TokenBroker; +import org.apache.polaris.service.auth.internal.broker.TokenResponse; import org.apache.polaris.service.types.TokenType; import org.assertj.core.api.Assertions; import org.assertj.core.api.InstanceOfAssertFactories; @@ -34,8 +36,10 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +@SuppressWarnings("resource") class DefaultOAuth2ApiServiceTest { private static final String CLIENT_CREDENTIALS = "client_credentials"; + private static final String TOKEN_EXCHANGE = "urn:ietf:params:oauth:grant-type:token-exchange"; private CallContext callContext; @@ -58,7 +62,7 @@ public void testNoSupportGrantType() { "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() .scope("scope") @@ -71,9 +75,7 @@ public void testNoSupportGrantType() { Assertions.assertThat(response.getEntity()) .isInstanceOf(OAuthTokenErrorResponse.class) .asInstanceOf(InstanceOfAssertFactories.type(OAuthTokenErrorResponse.class)) - .returns( - OAuthTokenErrorResponse.Error.unsupported_grant_type.name(), - OAuthTokenErrorResponse::getError); + .returns(OAuthError.unsupported_grant_type.name(), OAuthTokenErrorResponse::getError); } @Test @@ -89,7 +91,7 @@ public void testNoSupportRequestedTokenType() { "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() .scope("scope") @@ -102,9 +104,7 @@ public void testNoSupportRequestedTokenType() { Assertions.assertThat(response.getEntity()) .isInstanceOf(OAuthTokenErrorResponse.class) .asInstanceOf(InstanceOfAssertFactories.type(OAuthTokenErrorResponse.class)) - .returns( - OAuthTokenErrorResponse.Error.invalid_request.name(), - OAuthTokenErrorResponse::getError); + .returns(OAuthError.invalid_request.name(), OAuthTokenErrorResponse::getError); } @Test @@ -120,7 +120,7 @@ public void testSupportClientIdNoSecret() { "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() .scope("scope") @@ -148,7 +148,7 @@ public void testSupportClientIdAndSecret() { "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() .scope("scope") @@ -168,16 +168,16 @@ public void testSupportClientIdAndSecret() { public void testReadClientCredentialsFromAuthHeader() { RealmContext realmContext = () -> "realm"; TokenBroker tokenBroker = Mockito.mock(); - when(tokenBroker.supportsGrantType(TokenRequestValidator.TOKEN_EXCHANGE)).thenReturn(true); + when(tokenBroker.supportsGrantType(TOKEN_EXCHANGE)).thenReturn(true); when(tokenBroker.supportsRequestedTokenType(TokenType.ACCESS_TOKEN)).thenReturn(true); when(tokenBroker.generateFromClientSecrets( "client", "secret", - TokenRequestValidator.TOKEN_EXCHANGE, + TOKEN_EXCHANGE, "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() .authHeader( @@ -185,7 +185,7 @@ public void testReadClientCredentialsFromAuthHeader() { + Base64.getEncoder() .encodeToString("client:secret".getBytes(Charset.defaultCharset()))) .scope("scope") - .grantType(TokenRequestValidator.TOKEN_EXCHANGE) + .grantType(TOKEN_EXCHANGE) .requestedTokenType(TokenType.ACCESS_TOKEN) .realmContext(realmContext) .invoke(new DefaultOAuth2ApiService(tokenBroker, callContext)); @@ -199,16 +199,16 @@ public void testReadClientCredentialsFromAuthHeader() { public void testAuthHeaderRequiresValidCredentialPair() { RealmContext realmContext = () -> "realm"; TokenBroker tokenBroker = Mockito.mock(); - when(tokenBroker.supportsGrantType(TokenRequestValidator.TOKEN_EXCHANGE)).thenReturn(true); + when(tokenBroker.supportsGrantType(TOKEN_EXCHANGE)).thenReturn(true); when(tokenBroker.supportsRequestedTokenType(TokenType.ACCESS_TOKEN)).thenReturn(true); when(tokenBroker.generateFromClientSecrets( null, "secret", - TokenRequestValidator.TOKEN_EXCHANGE, + TOKEN_EXCHANGE, "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() .authHeader( @@ -216,33 +216,31 @@ public void testAuthHeaderRequiresValidCredentialPair() { + Base64.getEncoder() .encodeToString("secret".getBytes(Charset.defaultCharset()))) .scope("scope") - .grantType(TokenRequestValidator.TOKEN_EXCHANGE) + .grantType(TOKEN_EXCHANGE) .requestedTokenType(TokenType.ACCESS_TOKEN) .realmContext(realmContext) .invoke(new DefaultOAuth2ApiService(tokenBroker, callContext)); Assertions.assertThat(response.getEntity()) .isInstanceOf(OAuthTokenErrorResponse.class) .asInstanceOf(InstanceOfAssertFactories.type(OAuthTokenErrorResponse.class)) - .returns( - OAuthTokenErrorResponse.Error.invalid_request.name(), - OAuthTokenErrorResponse::getError); + .returns(OAuthError.invalid_request.name(), OAuthTokenErrorResponse::getError); } @Test public void testReadClientSecretFromAuthHeader() { RealmContext realmContext = () -> "realm"; TokenBroker tokenBroker = Mockito.mock(); - when(tokenBroker.supportsGrantType(TokenRequestValidator.TOKEN_EXCHANGE)).thenReturn(true); + when(tokenBroker.supportsGrantType(TOKEN_EXCHANGE)).thenReturn(true); when(tokenBroker.supportsRequestedTokenType(TokenType.ACCESS_TOKEN)).thenReturn(true); when(tokenBroker.generateFromClientSecrets( "", "secret", - TokenRequestValidator.TOKEN_EXCHANGE, + TOKEN_EXCHANGE, "scope", callContext.getPolarisCallContext(), TokenType.ACCESS_TOKEN)) - .thenReturn(new TokenResponse("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); + .thenReturn(TokenResponse.of("token", TokenType.ACCESS_TOKEN.getValue(), 3600)); Response response = new InvocationBuilder() @@ -252,7 +250,7 @@ public void testReadClientSecretFromAuthHeader() { + Base64.getEncoder() .encodeToString(":secret".getBytes(Charset.defaultCharset()))) .scope("scope") - .grantType(TokenRequestValidator.TOKEN_EXCHANGE) + .grantType(TOKEN_EXCHANGE) .requestedTokenType(TokenType.ACCESS_TOKEN) .realmContext(realmContext) .invoke(new DefaultOAuth2ApiService(tokenBroker, callContext)); diff --git a/runtime/service/src/test/java/org/apache/polaris/service/test/PolarisIntegrationTestFixture.java b/runtime/service/src/test/java/org/apache/polaris/service/test/PolarisIntegrationTestFixture.java index f8df9a1017..19900e1200 100644 --- a/runtime/service/src/test/java/org/apache/polaris/service/test/PolarisIntegrationTestFixture.java +++ b/runtime/service/src/test/java/org/apache/polaris/service/test/PolarisIntegrationTestFixture.java @@ -40,7 +40,7 @@ import org.apache.polaris.core.persistence.BasePersistence; import org.apache.polaris.core.persistence.PolarisMetaStoreManager; import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet; -import org.apache.polaris.service.auth.TokenUtils; +import org.apache.polaris.service.auth.internal.broker.TokenUtils; import org.apache.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory; import org.junit.jupiter.api.TestInfo; import org.slf4j.Logger;