From ad562b491724a11e10be5590a5079a9264589960 Mon Sep 17 00:00:00 2001 From: guqing <38999863+guqing@users.noreply.github.com> Date: Tue, 19 Apr 2022 16:06:09 +0800 Subject: [PATCH] feat: Add InMemoryOAuth2AuthorizationService for development or testing (#1854) --- .../halo/app/config/WebSecurityConfig.java | 5 +- .../InMemoryOAuth2AuthorizationService.java | 169 ++++++++++++ .../JwtDaoAuthenticationProvider.java | 21 +- .../authentication/OAuth2Authorization.java | 26 ++ .../OAuth2AuthorizationService.java | 54 ++-- ...nMemoryOAuth2AuthorizationServiceTest.java | 240 ++++++++++++++++++ 6 files changed, 480 insertions(+), 35 deletions(-) create mode 100644 src/main/java/run/halo/app/identity/authentication/InMemoryOAuth2AuthorizationService.java create mode 100644 src/test/java/run/halo/app/authentication/InMemoryOAuth2AuthorizationServiceTest.java diff --git a/src/main/java/run/halo/app/config/WebSecurityConfig.java b/src/main/java/run/halo/app/config/WebSecurityConfig.java index 22df1a8ff..50cf3d20d 100644 --- a/src/main/java/run/halo/app/config/WebSecurityConfig.java +++ b/src/main/java/run/halo/app/config/WebSecurityConfig.java @@ -30,10 +30,10 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import run.halo.app.identity.authentication.InMemoryOAuth2AuthorizationService; import run.halo.app.identity.authentication.JwtDaoAuthenticationProvider; import run.halo.app.identity.authentication.JwtGenerator; import run.halo.app.identity.authentication.JwtUsernamePasswordAuthenticationFilter; -import run.halo.app.identity.authentication.OAuth2AuthorizationService; import run.halo.app.identity.authentication.ProviderContextFilter; import run.halo.app.identity.authentication.ProviderSettings; import run.halo.app.identity.entrypoint.JwtAccessDeniedHandler; @@ -110,7 +110,8 @@ public class WebSecurityConfig { @Bean JwtDaoAuthenticationProvider jwtDaoAuthenticationProvider() { JwtDaoAuthenticationProvider authenticationProvider = - new JwtDaoAuthenticationProvider(jwtGenerator(), new OAuth2AuthorizationService()); + new JwtDaoAuthenticationProvider(jwtGenerator(), + new InMemoryOAuth2AuthorizationService()); authenticationProvider.setUserDetailsService(userDetailsService()); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; diff --git a/src/main/java/run/halo/app/identity/authentication/InMemoryOAuth2AuthorizationService.java b/src/main/java/run/halo/app/identity/authentication/InMemoryOAuth2AuthorizationService.java new file mode 100644 index 000000000..61fbe84d7 --- /dev/null +++ b/src/main/java/run/halo/app/identity/authentication/InMemoryOAuth2AuthorizationService.java @@ -0,0 +1,169 @@ +package run.halo.app.identity.authentication; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.springframework.lang.Nullable; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.util.Assert; + +/** + * An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s in-memory.

+ * NOTE: This implementation should ONLY be used during development/testing. + * + * @author guqing + * @see OAuth2AuthorizationService + * @since 2.0.0 + */ +public class InMemoryOAuth2AuthorizationService implements OAuth2AuthorizationService { + private int maxInitializedAuthorizations = 100; + + /* + * Stores "initialized" (uncompleted) authorizations, where an access token has not yet been + * granted. + * This state occurs with the authorization_code grant flow during the user consent step OR + * when the code is returned in the authorization response but the access token request is + * not yet initiated. + */ + private Map initializedAuthorizations = + Collections.synchronizedMap(new MaxSizeHashMap<>(this.maxInitializedAuthorizations)); + + /* + * Stores "completed" authorizations, where an access token has been granted. + */ + private final Map authorizations = new ConcurrentHashMap<>(); + + /* + * Constructor used for testing only. + */ + public InMemoryOAuth2AuthorizationService(int maxInitializedAuthorizations) { + this.maxInitializedAuthorizations = maxInitializedAuthorizations; + this.initializedAuthorizations = + Collections.synchronizedMap(new MaxSizeHashMap<>(this.maxInitializedAuthorizations)); + } + + /** + * Constructs an {@code InMemoryOAuth2AuthorizationService}. + */ + public InMemoryOAuth2AuthorizationService() { + this(Collections.emptyList()); + } + + /** + * Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided parameters. + * + * @param authorizations the authorization(s) + */ + public InMemoryOAuth2AuthorizationService(OAuth2Authorization... authorizations) { + this(Arrays.asList(authorizations)); + } + + /** + * Constructs an {@code InMemoryOAuth2AuthorizationService} using the provided parameters. + * + * @param authorizations the authorization(s) + */ + public InMemoryOAuth2AuthorizationService(List authorizations) { + Assert.notNull(authorizations, "authorizations cannot be null"); + authorizations.forEach(authorization -> { + Assert.notNull(authorization, "authorization cannot be null"); + Assert.isTrue(!this.authorizations.containsKey(authorization.getId()), + "The authorization must be unique. Found duplicate identifier: " + + authorization.getId()); + this.authorizations.put(authorization.getId(), authorization); + }); + } + + @Override + public void save(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); + if (isComplete(authorization)) { + this.authorizations.put(authorization.getId(), authorization); + } else { + this.initializedAuthorizations.put(authorization.getId(), authorization); + } + } + + @Override + public void remove(OAuth2Authorization authorization) { + Assert.notNull(authorization, "authorization cannot be null"); + if (isComplete(authorization)) { + this.authorizations.remove(authorization.getId(), authorization); + } else { + this.initializedAuthorizations.remove(authorization.getId(), authorization); + } + } + + @Nullable + @Override + public OAuth2Authorization findById(String id) { + Assert.hasText(id, "id cannot be empty"); + OAuth2Authorization authorization = this.authorizations.get(id); + return authorization != null + ? authorization : + this.initializedAuthorizations.get(id); + } + + @Nullable + @Override + public OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType) { + Assert.hasText(token, "token cannot be empty"); + for (OAuth2Authorization authorization : this.authorizations.values()) { + if (hasToken(authorization, token, tokenType)) { + return authorization; + } + } + for (OAuth2Authorization authorization : this.initializedAuthorizations.values()) { + if (hasToken(authorization, token, tokenType)) { + return authorization; + } + } + return null; + } + + private static boolean isComplete(OAuth2Authorization authorization) { + return authorization.getAccessToken() != null; + } + + private static boolean hasToken(OAuth2Authorization authorization, String token, + @Nullable OAuth2TokenType tokenType) { + if (tokenType == null) { + return matchesAccessToken(authorization, token) + || matchesRefreshToken(authorization, token); + } else if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) { + return matchesAccessToken(authorization, token); + } else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) { + return matchesRefreshToken(authorization, token); + } + return false; + } + + private static boolean matchesAccessToken(OAuth2Authorization authorization, String token) { + OAuth2Authorization.Token accessToken = + authorization.getToken(OAuth2AccessToken.class); + return accessToken != null && accessToken.getToken().getTokenValue().equals(token); + } + + private static boolean matchesRefreshToken(OAuth2Authorization authorization, String token) { + OAuth2Authorization.Token refreshToken = + authorization.getToken(OAuth2RefreshToken.class); + return refreshToken != null && refreshToken.getToken().getTokenValue().equals(token); + } + + private static final class MaxSizeHashMap extends LinkedHashMap { + private final int maxSize; + + private MaxSizeHashMap(int maxSize) { + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > this.maxSize; + } + } +} diff --git a/src/main/java/run/halo/app/identity/authentication/JwtDaoAuthenticationProvider.java b/src/main/java/run/halo/app/identity/authentication/JwtDaoAuthenticationProvider.java index 28b56504f..79397b8a2 100644 --- a/src/main/java/run/halo/app/identity/authentication/JwtDaoAuthenticationProvider.java +++ b/src/main/java/run/halo/app/identity/authentication/JwtDaoAuthenticationProvider.java @@ -8,6 +8,7 @@ import org.springframework.security.authentication.dao.DaoAuthenticationProvider import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClaimAccessor; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; @@ -21,7 +22,6 @@ import org.springframework.security.oauth2.core.OAuth2Token; * @since 2.0.0 */ public class JwtDaoAuthenticationProvider extends DaoAuthenticationProvider { - private static final OAuth2TokenType PASSWORD_TOKEN = new OAuth2TokenType("password"); private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; private final OAuth2TokenGenerator tokenGenerator; @@ -41,18 +41,6 @@ public class JwtDaoAuthenticationProvider extends DaoAuthenticationProvider { (UsernamePasswordAuthenticationToken) super.createSuccessAuthentication(principal, authentication, user); - OAuth2Authorization authorization = this.authorizationService.findByUsername( - usernamePasswordAuthenticationToken.getName(), PASSWORD_TOKEN); - - OAuth2Authorization.Token refreshToken = - authorization.getRefreshToken(); - if (refreshToken == null || !refreshToken.isActive()) { - // As per https://tools.ietf.org/html/rfc6749#section-5.2 - // invalid_grant: The provided authorization grant (e.g., authorization code, - // resource owner credentials) or refresh token is invalid, expired, revoked [...]. - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT); - } - Set scopes = usernamePasswordAuthenticationToken.getAuthorities().stream() .map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); @@ -61,7 +49,10 @@ public class JwtDaoAuthenticationProvider extends DaoAuthenticationProvider { .providerContext(ProviderContextHolder.getProviderContext()) .authorizedScopes(scopes); - OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.from(authorization); + OAuth2Authorization.Builder authorizationBuilder = new OAuth2Authorization.Builder() + .principalName(authentication.getName()) + .authorizationGrantType(AuthorizationGrantType.PASSWORD) + .attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, scopes); // ----- Access token ----- OAuth2TokenContext tokenContext = @@ -89,7 +80,7 @@ public class JwtDaoAuthenticationProvider extends DaoAuthenticationProvider { ProviderContextHolder.getProviderContext().providerSettings(); // ----- Refresh token ----- - OAuth2RefreshToken currentRefreshToken = refreshToken.getToken(); + OAuth2RefreshToken currentRefreshToken = null; if (!providerSettings.isReuseRefreshTokens()) { tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); diff --git a/src/main/java/run/halo/app/identity/authentication/OAuth2Authorization.java b/src/main/java/run/halo/app/identity/authentication/OAuth2Authorization.java index d3739b5a0..dac682d49 100644 --- a/src/main/java/run/halo/app/identity/authentication/OAuth2Authorization.java +++ b/src/main/java/run/halo/app/identity/authentication/OAuth2Authorization.java @@ -9,6 +9,7 @@ import java.util.Objects; import java.util.UUID; import java.util.function.Consumer; import org.springframework.lang.Nullable; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.OAuth2Token; @@ -31,6 +32,7 @@ public class OAuth2Authorization implements Serializable { private String id; private String principalName; + private AuthorizationGrantType authorizationGrantType; private Map, Token> tokens; private Map attributes; @@ -55,6 +57,16 @@ public class OAuth2Authorization implements Serializable { return this.principalName; } + /** + * Returns the {@link AuthorizationGrantType authorization grant type} used for the + * authorization. + * + * @return the {@link AuthorizationGrantType} used for the authorization + */ + public AuthorizationGrantType getAuthorizationGrantType() { + return this.authorizationGrantType; + } + /** * Returns the {@link Token} of type {@link OAuth2AccessToken}. * @@ -319,6 +331,7 @@ public class OAuth2Authorization implements Serializable { public static class Builder implements Serializable { private String id; private String principalName; + private AuthorizationGrantType authorizationGrantType; private Map, Token> tokens = new HashMap<>(); private final Map attributes = new HashMap<>(); @@ -344,6 +357,18 @@ public class OAuth2Authorization implements Serializable { return this; } + /** + * Sets the {@link AuthorizationGrantType authorization grant type} used for the + * authorization. + * + * @param authorizationGrantType the {@link AuthorizationGrantType} + * @return the {@link Builder} + */ + public Builder authorizationGrantType(AuthorizationGrantType authorizationGrantType) { + this.authorizationGrantType = authorizationGrantType; + return this; + } + /** * Sets the {@link OAuth2AccessToken access token}. * @@ -444,6 +469,7 @@ public class OAuth2Authorization implements Serializable { } authorization.id = this.id; authorization.principalName = this.principalName; + authorization.authorizationGrantType = this.authorizationGrantType; authorization.tokens = Collections.unmodifiableMap(this.tokens); authorization.attributes = Collections.unmodifiableMap(this.attributes); return authorization; diff --git a/src/main/java/run/halo/app/identity/authentication/OAuth2AuthorizationService.java b/src/main/java/run/halo/app/identity/authentication/OAuth2AuthorizationService.java index 74da9cbd6..7e5bb5487 100644 --- a/src/main/java/run/halo/app/identity/authentication/OAuth2AuthorizationService.java +++ b/src/main/java/run/halo/app/identity/authentication/OAuth2AuthorizationService.java @@ -1,27 +1,45 @@ package run.halo.app.identity.authentication; -import java.time.Instant; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.lang.Nullable; /** * @author guqing * @since 2.0.0 */ -public class OAuth2AuthorizationService { - OAuth2Authorization findByUsername(String username, OAuth2TokenType oauth2TokenType) { - // TODO to be implementation - return new OAuth2Authorization.Builder().id("id") - .accessToken(new OAuth2AccessToken( - OAuth2AccessToken.TokenType.BEARER, "token", Instant.now(), - Instant.now().plusMillis(123))) - .refreshToken( - new OAuth2RefreshToken("refresh_token", Instant.now())) - .principalName("guqing") - .build(); - } +public interface OAuth2AuthorizationService { + /** + * Returns the {@link OAuth2Authorization} containing the provided {@code token}, + * or {@code null} if not found. + * + * @param token the token credential + * @param tokenType the {@link OAuth2TokenType token type} + * @return the {@link OAuth2Authorization} if found, otherwise {@code null} + */ + @Nullable + OAuth2Authorization findByToken(String token, @Nullable OAuth2TokenType tokenType); + + /** + * Saves the {@link OAuth2Authorization}. + * + * @param authorization the {@link OAuth2Authorization} + */ + void save(OAuth2Authorization authorization); + + /** + * Removes the {@link OAuth2Authorization}. + * + * @param authorization the {@link OAuth2Authorization} + */ + void remove(OAuth2Authorization authorization); + + /** + * Returns the {@link OAuth2Authorization} identified by the provided {@code id}, + * or {@code null} if not found. + * + * @param id the authorization identifier + * @return the {@link OAuth2Authorization} if found, otherwise {@code null} + */ + @Nullable + OAuth2Authorization findById(String id); - void save(OAuth2Authorization authorization) { - // TODO to be implementation - } } diff --git a/src/test/java/run/halo/app/authentication/InMemoryOAuth2AuthorizationServiceTest.java b/src/test/java/run/halo/app/authentication/InMemoryOAuth2AuthorizationServiceTest.java new file mode 100644 index 000000000..1dfcfe190 --- /dev/null +++ b/src/test/java/run/halo/app/authentication/InMemoryOAuth2AuthorizationServiceTest.java @@ -0,0 +1,240 @@ +package run.halo.app.authentication; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.time.Instant; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import run.halo.app.identity.authentication.InMemoryOAuth2AuthorizationService; +import run.halo.app.identity.authentication.OAuth2Authorization; +import run.halo.app.identity.authentication.OAuth2AuthorizationService; +import run.halo.app.identity.authentication.OAuth2TokenType; + +/** + * An {@link OAuth2AuthorizationService} that stores {@link OAuth2Authorization}'s in-memory.

+ * NOTE: This implementation should ONLY be used during development/testing. + * + * @author guqing + * @see OAuth2AuthorizationService + * @since 2.0.0 + */ +public class InMemoryOAuth2AuthorizationServiceTest { + private static final String ID = "id"; + private static final String PRINCIPAL_NAME = "principal"; + private static final AuthorizationGrantType AUTHORIZATION_GRANT_TYPE = + AuthorizationGrantType.PASSWORD; + private static final OAuth2AccessToken TOKEN = + new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access_token", + Instant.now(), Instant.now().plusSeconds(256)); + private InMemoryOAuth2AuthorizationService authorizationService; + + @BeforeEach + public void setup() { + this.authorizationService = new InMemoryOAuth2AuthorizationService(); + } + + @Test + public void constructorVarargsWhenAuthorizationNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new InMemoryOAuth2AuthorizationService((OAuth2Authorization) null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("authorization cannot be null"); + } + + @Test + public void constructorListWhenAuthorizationsNullThenThrowIllegalArgumentException() { + assertThatThrownBy( + () -> new InMemoryOAuth2AuthorizationService((List) null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("authorizations cannot be null"); + } + + @Test + public void constructorWhenDuplicateAuthorizationsThenThrowIllegalArgumentException() { + OAuth2Authorization authorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .token(TOKEN) + .build(); + + assertThatThrownBy( + () -> new InMemoryOAuth2AuthorizationService(authorization, authorization)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("The authorization must be unique. Found duplicate identifier: id"); + } + + @Test + public void saveWhenAuthorizationNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizationService.save(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("authorization cannot be null"); + } + + @Test + public void saveWhenAuthorizationNewThenSaved() { + OAuth2Authorization expectedAuthorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .token(TOKEN) + .build(); + this.authorizationService.save(expectedAuthorization); + + OAuth2Authorization authorization = this.authorizationService.findByToken( + TOKEN.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN); + assertThat(authorization).isEqualTo(expectedAuthorization); + } + + @Test + public void saveWhenAuthorizationExistsThenUpdated() { + OAuth2Authorization originalAuthorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .token(TOKEN) + .build(); + this.authorizationService.save(originalAuthorization); + + OAuth2Authorization authorization = this.authorizationService.findById( + originalAuthorization.getId()); + assertThat(authorization).isEqualTo(originalAuthorization); + + OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization) + .attribute("custom-name-1", "custom-value-1") + .build(); + this.authorizationService.save(updatedAuthorization); + + authorization = this.authorizationService.findById( + updatedAuthorization.getId()); + assertThat(authorization).isEqualTo(updatedAuthorization); + assertThat(authorization).isNotEqualTo(originalAuthorization); + } + + @Test + public void saveWhenInitializedAuthorizationsReachMaxThenOldestRemoved() { + int maxInitializedAuthorizations = 5; + InMemoryOAuth2AuthorizationService authorizationService = + new InMemoryOAuth2AuthorizationService(maxInitializedAuthorizations); + + OAuth2Authorization initialAuthorization = new OAuth2Authorization.Builder() + .id(ID + "-initial") + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .attribute(OAuth2ParameterNames.STATE, "state-initial") + .build(); + authorizationService.save(initialAuthorization); + + OAuth2Authorization authorization = + authorizationService.findById(initialAuthorization.getId()); + assertThat(authorization).isEqualTo(initialAuthorization); + } + + @Test + public void removeWhenAuthorizationNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizationService.remove(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("authorization cannot be null"); + } + + @Test + public void removeWhenAuthorizationProvidedThenRemoved() { + OAuth2Authorization expectedAuthorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .token(TOKEN) + .build(); + + this.authorizationService.save(expectedAuthorization); + OAuth2Authorization authorization = this.authorizationService.findByToken( + TOKEN.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN); + assertThat(authorization).isEqualTo(expectedAuthorization); + + this.authorizationService.remove(expectedAuthorization); + authorization = this.authorizationService.findByToken( + TOKEN.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN); + assertThat(authorization).isNull(); + } + + @Test + public void findByIdWhenIdNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizationService.findById(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("id cannot be empty"); + } + + @Test + public void findByTokenWhenTokenNullThenThrowIllegalArgumentException() { + assertThatThrownBy( + () -> this.authorizationService.findByToken(null, OAuth2TokenType.ACCESS_TOKEN)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("token cannot be empty"); + } + + @Test + public void findByTokenWhenAccessTokenExistsThenFound() { + OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, + "access-token", Instant.now().minusSeconds(60), Instant.now()); + OAuth2Authorization authorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .token(TOKEN) + .accessToken(accessToken) + .build(); + this.authorizationService.save(authorization); + + OAuth2Authorization result = this.authorizationService.findByToken( + accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN); + assertThat(authorization).isEqualTo(result); + result = this.authorizationService.findByToken(accessToken.getTokenValue(), null); + assertThat(authorization).isEqualTo(result); + } + + @Test + public void findByTokenWhenRefreshTokenExistsThenFound() { + OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now()); + OAuth2Authorization authorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .refreshToken(refreshToken) + .build(); + this.authorizationService.save(authorization); + + OAuth2Authorization result = this.authorizationService.findByToken( + refreshToken.getTokenValue(), OAuth2TokenType.REFRESH_TOKEN); + assertThat(authorization).isEqualTo(result); + result = this.authorizationService.findByToken(refreshToken.getTokenValue(), null); + assertThat(authorization).isEqualTo(result); + } + + @Test + public void findByTokenWhenWrongTokenTypeThenNotFound() { + OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now()); + OAuth2Authorization authorization = new OAuth2Authorization.Builder() + .id(ID) + .principalName(PRINCIPAL_NAME) + .authorizationGrantType(AUTHORIZATION_GRANT_TYPE) + .refreshToken(refreshToken) + .build(); + this.authorizationService.save(authorization); + + OAuth2Authorization result = this.authorizationService.findByToken( + refreshToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN); + assertThat(result).isNull(); + } + + @Test + public void findByTokenWhenTokenDoesNotExistThenNull() { + OAuth2Authorization result = this.authorizationService.findByToken( + "access-token", OAuth2TokenType.ACCESS_TOKEN); + assertThat(result).isNull(); + } +}