From 704f98688d4a2fba6ba5be48c325ce3d4fd27b2b Mon Sep 17 00:00:00 2001 From: Nikita Konev Date: Mon, 13 Jan 2020 00:59:55 +0300 Subject: [PATCH] Make OAuth2AccessTokenResponse converters public --- .../OAuth2AccessTokenResponseConverter.java | 84 ++++++++++++ ...cessTokenResponseHttpMessageConverter.java | 103 +------------- ...ccessTokenResponseParametersConverter.java | 66 +++++++++ ...Auth2AccessTokenResponseConverterTest.java | 126 ++++++++++++++++++ ...sTokenResponseParametersConverterTest.java | 89 +++++++++++++ 5 files changed, 366 insertions(+), 102 deletions(-) create mode 100644 oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverter.java create mode 100644 oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverter.java create mode 100644 oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverterTest.java create mode 100644 oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverterTest.java diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverter.java new file mode 100644 index 0000000000..6d3eddc737 --- /dev/null +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverter.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.oauth2.core.http.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.util.StringUtils; + +import java.util.*; + +/** + * A {@link Converter} that converts the provided + * OAuth 2.0 Access Token Response parameters to an {@link OAuth2AccessTokenResponse}. + * + * @author Joe Grandja + * @author Nikita Konev + * @since 5.3 + */ +public final class OAuth2AccessTokenResponseConverter implements Converter, OAuth2AccessTokenResponse> { + private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = new HashSet<>(Arrays.asList( + OAuth2ParameterNames.ACCESS_TOKEN, + OAuth2ParameterNames.EXPIRES_IN, + OAuth2ParameterNames.REFRESH_TOKEN, + OAuth2ParameterNames.SCOPE, + OAuth2ParameterNames.TOKEN_TYPE + )); + + @Override + public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { + String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); + + OAuth2AccessToken.TokenType accessTokenType = null; + if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase( + tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) { + accessTokenType = OAuth2AccessToken.TokenType.BEARER; + } + + long expiresIn = 0; + if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) { + try { + expiresIn = Long.parseLong(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN)); + } catch (NumberFormatException ex) { + } + } + + Set scopes = Collections.emptySet(); + if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { + String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE); + scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); + } + + String refreshToken = tokenResponseParameters.get(OAuth2ParameterNames.REFRESH_TOKEN); + + Map additionalParameters = new LinkedHashMap<>(); + for (Map.Entry entry : tokenResponseParameters.entrySet()) { + if (!TOKEN_RESPONSE_PARAMETER_NAMES.contains(entry.getKey())) { + additionalParameters.put(entry.getKey(), entry.getValue()); + } + } + + return OAuth2AccessTokenResponse.withToken(accessToken) + .tokenType(accessTokenType) + .expiresIn(expiresIn) + .scopes(scopes) + .refreshToken(refreshToken) + .additionalParameters(additionalParameters) + .build(); + } +} diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java index d3c0a439c2..ec70ff28e8 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,24 +25,12 @@ import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashSet; import java.util.Map; -import java.util.Set; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.HashMap; /** * A {@link HttpMessageConverter} for an {@link OAuth2AccessTokenResponse OAuth 2.0 Access Token Response}. @@ -125,93 +113,4 @@ public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpM this.tokenResponseParametersConverter = tokenResponseParametersConverter; } - /** - * A {@link Converter} that converts the provided - * OAuth 2.0 Access Token Response parameters to an {@link OAuth2AccessTokenResponse}. - */ - private static class OAuth2AccessTokenResponseConverter implements Converter, OAuth2AccessTokenResponse> { - private static final Set TOKEN_RESPONSE_PARAMETER_NAMES = new HashSet<>(Arrays.asList( - OAuth2ParameterNames.ACCESS_TOKEN, - OAuth2ParameterNames.TOKEN_TYPE, - OAuth2ParameterNames.EXPIRES_IN, - OAuth2ParameterNames.REFRESH_TOKEN, - OAuth2ParameterNames.SCOPE - )); - - @Override - public OAuth2AccessTokenResponse convert(Map tokenResponseParameters) { - String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN); - - OAuth2AccessToken.TokenType accessTokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase( - tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - - long expiresIn = 0; - if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) { - try { - expiresIn = Long.parseLong(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN)); - } catch (NumberFormatException ex) { } - } - - Set scopes = Collections.emptySet(); - if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { - String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE); - scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); - } - - String refreshToken = tokenResponseParameters.get(OAuth2ParameterNames.REFRESH_TOKEN); - - Map additionalParameters = new LinkedHashMap<>(); - for (Map.Entry entry : tokenResponseParameters.entrySet()) { - if (!TOKEN_RESPONSE_PARAMETER_NAMES.contains(entry.getKey())) { - additionalParameters.put(entry.getKey(), entry.getValue()); - } - } - - return OAuth2AccessTokenResponse.withToken(accessToken) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .refreshToken(refreshToken) - .additionalParameters(additionalParameters) - .build(); - } - } - - /** - * A {@link Converter} that converts the provided {@link OAuth2AccessTokenResponse} - * to a {@code Map} representation of the OAuth 2.0 Access Token Response parameters. - */ - private static class OAuth2AccessTokenResponseParametersConverter implements Converter> { - - @Override - public Map convert(OAuth2AccessTokenResponse tokenResponse) { - Map parameters = new HashMap<>(); - - long expiresIn = -1; - if (tokenResponse.getAccessToken().getExpiresAt() != null) { - expiresIn = ChronoUnit.SECONDS.between(Instant.now(), tokenResponse.getAccessToken().getExpiresAt()); - } - - parameters.put(OAuth2ParameterNames.ACCESS_TOKEN, tokenResponse.getAccessToken().getTokenValue()); - parameters.put(OAuth2ParameterNames.TOKEN_TYPE, tokenResponse.getAccessToken().getTokenType().getValue()); - parameters.put(OAuth2ParameterNames.EXPIRES_IN, String.valueOf(expiresIn)); - if (!CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { - parameters.put(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(tokenResponse.getAccessToken().getScopes(), " ")); - } - if (tokenResponse.getRefreshToken() != null) { - parameters.put(OAuth2ParameterNames.REFRESH_TOKEN, tokenResponse.getRefreshToken().getTokenValue()); - } - if (!CollectionUtils.isEmpty(tokenResponse.getAdditionalParameters())) { - for (Map.Entry entry : tokenResponse.getAdditionalParameters().entrySet()) { - parameters.put(entry.getKey(), entry.getValue().toString()); - } - } - - return parameters; - } - } } diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverter.java new file mode 100644 index 0000000000..ce8ccad293 --- /dev/null +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverter.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.oauth2.core.http.converter; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link Converter} that converts the provided {@link OAuth2AccessTokenResponse} + * to a {@code Map} representation of the OAuth 2.0 Access Token Response parameters. + * + * @author Joe Grandja + * @author Nikita Konev + * @since 5.3 + */ +public final class OAuth2AccessTokenResponseParametersConverter implements Converter> { + + @Override + public Map convert(OAuth2AccessTokenResponse tokenResponse) { + Map parameters = new HashMap<>(); + + long expiresIn = -1; + if (tokenResponse.getAccessToken().getExpiresAt() != null) { + expiresIn = ChronoUnit.SECONDS.between(Instant.now(), tokenResponse.getAccessToken().getExpiresAt()); + } + + parameters.put(OAuth2ParameterNames.ACCESS_TOKEN, tokenResponse.getAccessToken().getTokenValue()); + parameters.put(OAuth2ParameterNames.TOKEN_TYPE, tokenResponse.getAccessToken().getTokenType().getValue()); + parameters.put(OAuth2ParameterNames.EXPIRES_IN, String.valueOf(expiresIn)); + if (!CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) { + parameters.put(OAuth2ParameterNames.SCOPE, + StringUtils.collectionToDelimitedString(tokenResponse.getAccessToken().getScopes(), " ")); + } + if (tokenResponse.getRefreshToken() != null) { + parameters.put(OAuth2ParameterNames.REFRESH_TOKEN, tokenResponse.getRefreshToken().getTokenValue()); + } + if (!CollectionUtils.isEmpty(tokenResponse.getAdditionalParameters())) { + for (Map.Entry entry : tokenResponse.getAdditionalParameters().entrySet()) { + parameters.put(entry.getKey(), entry.getValue().toString()); + } + } + + return parameters; + } +} diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverterTest.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverterTest.java new file mode 100644 index 0000000000..fd55d8e3ed --- /dev/null +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseConverterTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.oauth2.core.http.converter; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2RefreshToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Tests for {@link OAuth2AccessTokenResponseConverter}. + * + * @author Nikita Konev + */ +public class OAuth2AccessTokenResponseConverterTest { + + private OAuth2AccessTokenResponseConverter messageConverter; + + @Before + public void setup() { + this.messageConverter = new OAuth2AccessTokenResponseConverter(); + } + + + @Test + public void shouldConvertFull() { + Map map = new HashMap<>(); + map.put("access_token", "access-token-1234"); + map.put("token_type", "bearer"); + map.put("expires_in", "3600"); + map.put("scope", "read write"); + map.put("refresh_token", "refresh-token-1234"); + map.put("custom_parameter_1", "custom-value-1"); + map.put("custom_parameter_2", "custom-value-2"); + OAuth2AccessTokenResponse converted = messageConverter.convert(map); + OAuth2AccessToken accessToken = converted.getAccessToken(); + Assert.assertNotNull(accessToken); + Assert.assertEquals("access-token-1234", accessToken.getTokenValue()); + Assert.assertEquals(OAuth2AccessToken.TokenType.BEARER, accessToken.getTokenType()); + Set scopes = accessToken.getScopes(); + Assert.assertNotNull(scopes); + Assert.assertEquals(2, scopes.size()); + Assert.assertTrue(scopes.contains("read")); + Assert.assertTrue(scopes.contains("write")); + Assert.assertEquals(3600, Duration.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()).getSeconds()); + + OAuth2RefreshToken refreshToken = converted.getRefreshToken(); + Assert.assertNotNull(refreshToken); + Assert.assertEquals("refresh-token-1234", refreshToken.getTokenValue()); + + Map additionalParameters = converted.getAdditionalParameters(); + Assert.assertNotNull(additionalParameters); + Assert.assertEquals(2, additionalParameters.size()); + Assert.assertEquals("custom-value-1", additionalParameters.get("custom_parameter_1")); + Assert.assertEquals("custom-value-2", additionalParameters.get("custom_parameter_2")); + } + + @Test + public void shouldConvertMinimal() { + Map map = new HashMap<>(); + map.put("access_token", "access-token-1234"); + map.put("token_type", "bearer"); + OAuth2AccessTokenResponse converted = messageConverter.convert(map); + OAuth2AccessToken accessToken = converted.getAccessToken(); + Assert.assertNotNull(accessToken); + Assert.assertEquals("access-token-1234", accessToken.getTokenValue()); + Assert.assertEquals(OAuth2AccessToken.TokenType.BEARER, accessToken.getTokenType()); + Set scopes = accessToken.getScopes(); + Assert.assertNotNull(scopes); + Assert.assertEquals(0, scopes.size()); + + Assert.assertEquals(1, Duration.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()).getSeconds()); + + OAuth2RefreshToken refreshToken = converted.getRefreshToken(); + Assert.assertNull(refreshToken); + + Map additionalParameters = converted.getAdditionalParameters(); + Assert.assertNotNull(additionalParameters); + Assert.assertEquals(0, additionalParameters.size()); + } + + @Test + public void shouldConvertWithUnsupportedExpiresIn() { + Map map = new HashMap<>(); + map.put("access_token", "access-token-1234"); + map.put("token_type", "bearer"); + map.put("expires_in", "2100-01-01-abc"); + OAuth2AccessTokenResponse converted = messageConverter.convert(map); + OAuth2AccessToken accessToken = converted.getAccessToken(); + Assert.assertNotNull(accessToken); + Assert.assertEquals("access-token-1234", accessToken.getTokenValue()); + Assert.assertEquals(OAuth2AccessToken.TokenType.BEARER, accessToken.getTokenType()); + Set scopes = accessToken.getScopes(); + Assert.assertNotNull(scopes); + Assert.assertEquals(0, scopes.size()); + + Assert.assertEquals(1, Duration.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()).getSeconds()); + + OAuth2RefreshToken refreshToken = converted.getRefreshToken(); + Assert.assertNull(refreshToken); + + Map additionalParameters = converted.getAdditionalParameters(); + Assert.assertNotNull(additionalParameters); + Assert.assertEquals(0, additionalParameters.size()); + } +} diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverterTest.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverterTest.java new file mode 100644 index 0000000000..1542efb172 --- /dev/null +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseParametersConverterTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.oauth2.core.http.converter; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; + +/** + * Tests for {@link OAuth2AccessTokenResponseParametersConverter}. + * + * @author Nikita Konev + */ +public class OAuth2AccessTokenResponseParametersConverterTest { + + private OAuth2AccessTokenResponseParametersConverter messageConverter; + + @Before + public void setup() { + this.messageConverter = new OAuth2AccessTokenResponseParametersConverter(); + } + + + @Test + public void convertFull() { + Map additionalParameters = new HashMap<>(); + additionalParameters.put("custom_parameter_1", "custom-value-1"); + additionalParameters.put("custom_parameter_2", "custom-value-2"); + + Set scopes = new HashSet<>(); + scopes.add("read"); + scopes.add("write"); + + OAuth2AccessTokenResponse build = OAuth2AccessTokenResponse + .withToken("access-token-value-1234") + .expiresIn(3699) + .additionalParameters(additionalParameters) + .refreshToken("refresh-token-value-1234") + .scopes(scopes) + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .build(); + Map result = messageConverter.convert(build); + Assert.assertEquals(7, result.size()); + + Assert.assertEquals("access-token-value-1234", result.get("access_token")); + Assert.assertEquals("refresh-token-value-1234", result.get("refresh_token")); + Assert.assertEquals("read write", result.get("scope")); + Assert.assertEquals("Bearer", result.get("token_type")); + Assert.assertNotNull(result.get("expires_in")); + Assert.assertEquals("custom-value-1", result.get("custom_parameter_1")); + Assert.assertEquals("custom-value-2", result.get("custom_parameter_2")); + } + + @Test + public void convertMinimal() { + OAuth2AccessTokenResponse build = OAuth2AccessTokenResponse + .withToken("access-token-value-1234") + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .build(); + Map result = messageConverter.convert(build); + Assert.assertEquals(3, result.size()); + + Assert.assertEquals("access-token-value-1234", result.get("access_token")); + Assert.assertEquals("Bearer", result.get("token_type")); + Assert.assertNotNull(result.get("expires_in")); + } +}