From a2f929add6e79f7d4d79e39c7ffb64fa3fd0fc12 Mon Sep 17 00:00:00 2001 From: bishoy basily Date: Tue, 14 Sep 2021 22:27:57 +0200 Subject: [PATCH 1/4] #gh10260 a setter added to set the BodyExtractor inside the AbstractWebClientReactiveOAuth2AccessTokenResponseClient --- ...ntReactiveOAuth2AccessTokenResponseClient.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java index efc22ae7c67..3318433837b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java @@ -22,6 +22,8 @@ import java.util.Collections; import java.util.Set; +import org.springframework.http.ReactiveHttpInputMessage; +import org.springframework.web.reactive.function.BodyExtractor; import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; @@ -67,6 +69,7 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient headersConverter = this::populateTokenRequestHeaders; + private BodyExtractor, ReactiveHttpInputMessage> bodyExtractor = OAuth2BodyExtractors.oauth2AccessTokenResponse(); AbstractWebClientReactiveOAuth2AccessTokenResponseClient() { } @@ -204,7 +207,7 @@ Set defaultScopes(T grantRequest) { * @return the token response from the response body. */ private Mono readTokenResponse(T grantRequest, ClientResponse response) { - return response.body(OAuth2BodyExtractors.oauth2AccessTokenResponse()) + return response.body(bodyExtractor) .map((tokenResponse) -> populateTokenResponse(grantRequest, tokenResponse)); } @@ -291,4 +294,14 @@ public final void addHeadersConverter(Converter headersConverter }; } + /** + * Sets the {@link BodyExtractor} that will be used to decode the {@link OAuth2AccessTokenResponse} + * + * @param bodyExtractor the {@link BodyExtractor} that will be used to decode the {@link OAuth2AccessTokenResponse} + * @since 5.6 + */ + public void setBodyExtractor(BodyExtractor, ReactiveHttpInputMessage> bodyExtractor) { + this.bodyExtractor = bodyExtractor; + } + } From 9cb88359e63676dd7c05e355424533cce620b9aa Mon Sep 17 00:00:00 2001 From: bishoy basily Date: Wed, 15 Sep 2021 20:51:16 +0200 Subject: [PATCH 2/4] Add setBodyExtractor Closes gh-10260 --- ...activeOAuth2AccessTokenResponseClient.java | 7 +- ...orizationCodeTokenResponseClientTests.java | 151 ++++++++++++++---- ...ntCredentialsTokenResponseClientTests.java | 94 ++++++++++- ...ctivePasswordTokenResponseClientTests.java | 93 ++++++++++- ...eRefreshTokenTokenResponseClientTests.java | 92 ++++++++++- 5 files changed, 397 insertions(+), 40 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java index 3318433837b..bcdffefc8cd 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java @@ -22,13 +22,12 @@ import java.util.Collections; import java.util.Set; -import org.springframework.http.ReactiveHttpInputMessage; -import org.springframework.web.reactive.function.BodyExtractor; import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; @@ -40,6 +39,7 @@ import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.BodyExtractor; /** * Abstract base class for all of the {@code WebClientReactive*TokenResponseClient}s that @@ -207,7 +207,7 @@ Set defaultScopes(T grantRequest) { * @return the token response from the response body. */ private Mono readTokenResponse(T grantRequest, ClientResponse response) { - return response.body(bodyExtractor) + return response.body(this.bodyExtractor) .map((tokenResponse) -> populateTokenResponse(grantRequest, tokenResponse)); } @@ -301,6 +301,7 @@ public final void addHeadersConverter(Converter headersConverter * @since 5.6 */ public void setBodyExtractor(BodyExtractor, ReactiveHttpInputMessage> bodyExtractor) { + Assert.notNull(bodyExtractor, "bodyExtractor cannot be null"); this.bodyExtractor = bodyExtractor; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java index 37d3768a8cf..351f9f43b0b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java @@ -17,10 +17,12 @@ package org.springframework.security.oauth2.client.endpoint; import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import com.nimbusds.oauth2.sdk.AccessTokenResponse; +import com.nimbusds.oauth2.sdk.TokenResponse; +import com.nimbusds.oauth2.sdk.token.AccessToken; +import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -28,10 +30,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -41,7 +45,10 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.web.reactive.function.BodyExtractor; +import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -80,14 +87,14 @@ public void cleanup() throws Exception { public void getTokenResponseWhenSuccessResponseThenReturnAccessTokenResponse() throws Exception { // @formatter:off String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"refresh_token\": \"refresh-token-1234\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n" - + "}\n"; + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); Instant expiresAtBefore = Instant.now().plusSeconds(3600); @@ -210,10 +217,10 @@ public void getTokenResponseWhenServerErrorResponseThenThrowOAuth2AuthorizationE public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAuth2AuthorizationException() { // @formatter:off String accessTokenSuccessResponse = "{\n" - + "\"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"not-bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; + + "\"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"not-bearer\",\n" + + " \"expires_in\": \"3600\"\n" + + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); assertThatExceptionOfType(OAuth2AuthorizationException.class) @@ -225,11 +232,11 @@ public void getTokenResponseWhenSuccessResponseAndNotBearerTokenTypeThenThrowOAu public void getTokenResponseWhenSuccessResponseIncludesScopeThenReturnAccessTokenResponseUsingResponseScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" - + "\"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\"\n" - + "}\n"; + + "\"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\"\n" + + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); this.clientRegistration.scope("openid", "profile", "email", "address"); @@ -242,10 +249,10 @@ public void getTokenResponseWhenSuccessResponseIncludesScopeThenReturnAccessToke public void getTokenResponseWhenSuccessResponseDoesNotIncludeScopeThenReturnAccessTokenResponseUsingRequestedScope() { // @formatter:off String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\"\n" + + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); this.clientRegistration.scope("openid", "profile", "email", "address"); @@ -284,11 +291,11 @@ public void setCustomWebClientThenCustomWebClientIsUsed() { this.tokenResponseClient.setWebClient(customClient); // @formatter:off String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\"\n" - + "}\n"; + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\"\n" + + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); this.clientRegistration.scope("openid", "profile", "email", "address"); @@ -302,10 +309,10 @@ public void getTokenResponseWhenOAuth2AuthorizationRequestContainsPkceParameters throws Exception { // @formatter:off String accessTokenSuccessResponse = "{\n" - + " \"access_token\": \"access-token-1234\",\n" - + " \"token_type\": \"bearer\",\n" - + " \"expires_in\": \"3600\"\n" - + "}\n"; + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": \"3600\"\n" + + "}\n"; // @formatter:on this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); this.tokenResponseClient.getTokenResponse(pkceAuthorizationCodeGrantRequest()).block(); @@ -409,4 +416,82 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { .isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ="); } + // gh-10260 + @Test + public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { + + // @formatter:off + String accessTokenSuccessResponse = "{\n" + + " \"Token\": \"access-token-1234\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"; + // @formatter:on + + WebClientReactiveAuthorizationCodeTokenResponseClient customClient = new WebClientReactiveAuthorizationCodeTokenResponseClient(); + customClient.setBodyExtractor((inputMessage, context) -> { + + BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors + .toMono(new ParameterizedTypeReference>() {}); + + return delegate.extract(inputMessage, context) + .flatMap(it -> { + + return Mono.fromCallable(() -> { + + it.put("access_token", it.get("Token")); + it.put("token_type", "bearer"); + + AccessTokenResponse accessTokenResponse = + (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); + + AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); + OAuth2AccessToken.TokenType accessTokenType = null; + if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { + accessTokenType = OAuth2AccessToken.TokenType.BEARER; + } + long expiresIn = accessToken.getLifetime(); + Set scopes = (accessToken.getScope() != null) ? + new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); + + String refreshToken = null; + if (accessTokenResponse.getTokens().getRefreshToken() != null) { + refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); + } + Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); + + // @formatter:off + return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) + .tokenType(accessTokenType) + .expiresIn(expiresIn) + .scopes(scopes) + .refreshToken(refreshToken) + .additionalParameters(additionalParameters) + .build(); + // @formatter:on + + }); + + }); + + }); + + this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); + Instant expiresAtBefore = Instant.now().plusSeconds(3600); + OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(authorizationCodeGrantRequest()).block(); + Instant expiresAtAfter = Instant.now().plusSeconds(3600); + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); + assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); + assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); + assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + + } + } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java index 28acf6bdfca..23f21eaa7ea 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java @@ -18,9 +18,13 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Collections; +import java.time.Instant; +import java.util.*; +import com.nimbusds.oauth2.sdk.AccessTokenResponse; +import com.nimbusds.oauth2.sdk.TokenResponse; +import com.nimbusds.oauth2.sdk.token.AccessToken; +import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -28,16 +32,22 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.web.reactive.function.BodyExtractor; +import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -280,4 +290,84 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { .isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ="); } + // gh-10260 + @Test + public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { + + // @formatter:off + enqueueJson("{\n" + + " \"Token\": \"access-token-1234\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"); + // @formatter:on + + WebClientReactiveClientCredentialsTokenResponseClient customClient = new WebClientReactiveClientCredentialsTokenResponseClient(); + customClient.setBodyExtractor((inputMessage, context) -> { + + BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors + .toMono(new ParameterizedTypeReference>() {}); + + return delegate.extract(inputMessage, context) + .flatMap(it -> { + + return Mono.fromCallable(() -> { + + it.put("access_token", it.get("Token")); + it.put("token_type", "bearer"); + + AccessTokenResponse accessTokenResponse = + (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); + + AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); + OAuth2AccessToken.TokenType accessTokenType = null; + if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { + accessTokenType = OAuth2AccessToken.TokenType.BEARER; + } + long expiresIn = accessToken.getLifetime(); + Set scopes = (accessToken.getScope() != null) ? + new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); + + String refreshToken = null; + if (accessTokenResponse.getTokens().getRefreshToken() != null) { + refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); + } + Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); + + // @formatter:off + return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) + .tokenType(accessTokenType) + .expiresIn(expiresIn) + .scopes(scopes) + .refreshToken(refreshToken) + .additionalParameters(additionalParameters) + .build(); + // @formatter:on + + }); + + }); + + }); + + OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest( + this.clientRegistration.build()); + + Instant expiresAtBefore = Instant.now().plusSeconds(3600); + OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(request).block(); + Instant expiresAtAfter = Instant.now().plusSeconds(3600); + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); + assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); + assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); + assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + + } + } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java index aabd789bc4a..867103fa2d5 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java @@ -17,8 +17,12 @@ package org.springframework.security.oauth2.client.endpoint; import java.time.Instant; -import java.util.Collections; +import java.util.*; +import com.nimbusds.oauth2.sdk.AccessTokenResponse; +import com.nimbusds.oauth2.sdk.TokenResponse; +import com.nimbusds.oauth2.sdk.token.AccessToken; +import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -26,16 +30,21 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.web.reactive.function.BodyExtractor; +import org.springframework.web.reactive.function.BodyExtractors; +import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -286,4 +295,86 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { .isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ="); } + // gh-10260 + @Test + public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { + + // @formatter:off + String accessTokenSuccessResponse = "{\n" + + " \"Token\": \"access-token-1234\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"; + // @formatter:on + + WebClientReactivePasswordTokenResponseClient customClient = new WebClientReactivePasswordTokenResponseClient(); + customClient.setBodyExtractor((inputMessage, context) -> { + + BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors + .toMono(new ParameterizedTypeReference>() {}); + + return delegate.extract(inputMessage, context) + .flatMap(it -> { + + return Mono.fromCallable(() -> { + + it.put("access_token", it.get("Token")); + it.put("token_type", "bearer"); + + AccessTokenResponse accessTokenResponse = + (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); + + AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); + OAuth2AccessToken.TokenType accessTokenType = null; + if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { + accessTokenType = OAuth2AccessToken.TokenType.BEARER; + } + long expiresIn = accessToken.getLifetime(); + Set scopes = (accessToken.getScope() != null) ? + new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); + + String refreshToken = null; + if (accessTokenResponse.getTokens().getRefreshToken() != null) { + refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); + } + Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); + + // @formatter:off + return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) + .tokenType(accessTokenType) + .expiresIn(expiresIn) + .scopes(scopes) + .refreshToken(refreshToken) + .additionalParameters(additionalParameters) + .build(); + // @formatter:on + + }); + + }); + + }); + + ClientRegistration clientRegistration = this.clientRegistrationBuilder.build(); + OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, + this.username, this.password); + + this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); + Instant expiresAtBefore = Instant.now().plusSeconds(3600); + OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(passwordGrantRequest).block(); + Instant expiresAtAfter = Instant.now().plusSeconds(3600); + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); + assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); + assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); + assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + + } + } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java index e94e1124cef..f69d402ae82 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java @@ -17,8 +17,12 @@ package org.springframework.security.oauth2.client.endpoint; import java.time.Instant; -import java.util.Collections; +import java.util.*; +import com.nimbusds.oauth2.sdk.AccessTokenResponse; +import com.nimbusds.oauth2.sdk.TokenResponse; +import com.nimbusds.oauth2.sdk.token.AccessToken; +import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -26,10 +30,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; +import org.springframework.http.ReactiveHttpInputMessage; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; @@ -39,6 +45,9 @@ import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.web.reactive.function.BodyExtractor; +import org.springframework.web.reactive.function.BodyExtractors; +import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -289,4 +298,85 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { .isEqualTo("Basic Y2xpZW50LWlkOmNsaWVudC1zZWNyZXQ="); } + // gh-10260 + @Test + public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { + + // @formatter:off + String accessTokenSuccessResponse = "{\n" + + " \"Token\": \"access-token-1234\",\n" + + " \"expires_in\": \"3600\",\n" + + " \"scope\": \"openid profile\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"; + // @formatter:on + + WebClientReactiveRefreshTokenTokenResponseClient customClient = new WebClientReactiveRefreshTokenTokenResponseClient(); + customClient.setBodyExtractor((inputMessage, context) -> { + + BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors + .toMono(new ParameterizedTypeReference>() {}); + + return delegate.extract(inputMessage, context) + .flatMap(it -> { + + return Mono.fromCallable(() -> { + + it.put("access_token", it.get("Token")); + it.put("token_type", "bearer"); + + AccessTokenResponse accessTokenResponse = + (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); + + AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); + OAuth2AccessToken.TokenType accessTokenType = null; + if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { + accessTokenType = OAuth2AccessToken.TokenType.BEARER; + } + long expiresIn = accessToken.getLifetime(); + Set scopes = (accessToken.getScope() != null) ? + new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); + + String refreshToken = null; + if (accessTokenResponse.getTokens().getRefreshToken() != null) { + refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); + } + Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); + + // @formatter:off + return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) + .tokenType(accessTokenType) + .expiresIn(expiresIn) + .scopes(scopes) + .refreshToken(refreshToken) + .additionalParameters(additionalParameters) + .build(); + // @formatter:on + + }); + + }); + + }); + + OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( + this.clientRegistrationBuilder.build(), this.accessToken, this.refreshToken); + + this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); + Instant expiresAtBefore = Instant.now().plusSeconds(3600); + OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(refreshTokenGrantRequest).block(); + Instant expiresAtAfter = Instant.now().plusSeconds(3600); + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); + assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); + assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); + assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); + assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + + } + } From 36a826943c176acf3fa45271cfce123a91e44097 Mon Sep 17 00:00:00 2001 From: bishoy basily Date: Thu, 16 Sep 2021 02:48:23 +0200 Subject: [PATCH 3/4] Temporary (one import sorting check style error) Add setBodyExtractor Closes gh-10260 --- ...activeOAuth2AccessTokenResponseClient.java | 16 ++-- ...orizationCodeTokenResponseClientTests.java | 85 +++---------------- ...ntCredentialsTokenResponseClientTests.java | 83 +++--------------- ...ctivePasswordTokenResponseClientTests.java | 81 +++--------------- ...eRefreshTokenTokenResponseClientTests.java | 80 ++--------------- 5 files changed, 52 insertions(+), 293 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java index bcdffefc8cd..2633d68ce15 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/AbstractWebClientReactiveOAuth2AccessTokenResponseClient.java @@ -36,10 +36,10 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.BodyExtractor; /** * Abstract base class for all of the {@code WebClientReactive*TokenResponseClient}s that @@ -69,7 +69,9 @@ public abstract class AbstractWebClientReactiveOAuth2AccessTokenResponseClient headersConverter = this::populateTokenRequestHeaders; - private BodyExtractor, ReactiveHttpInputMessage> bodyExtractor = OAuth2BodyExtractors.oauth2AccessTokenResponse(); + + private BodyExtractor, ReactiveHttpInputMessage> bodyExtractor = OAuth2BodyExtractors + .oauth2AccessTokenResponse(); AbstractWebClientReactiveOAuth2AccessTokenResponseClient() { } @@ -295,12 +297,14 @@ public final void addHeadersConverter(Converter headersConverter } /** - * Sets the {@link BodyExtractor} that will be used to decode the {@link OAuth2AccessTokenResponse} - * - * @param bodyExtractor the {@link BodyExtractor} that will be used to decode the {@link OAuth2AccessTokenResponse} + * Sets the {@link BodyExtractor} that will be used to decode the + * {@link OAuth2AccessTokenResponse} + * @param bodyExtractor the {@link BodyExtractor} that will be used to decode the + * {@link OAuth2AccessTokenResponse} * @since 5.6 */ - public void setBodyExtractor(BodyExtractor, ReactiveHttpInputMessage> bodyExtractor) { + public void setBodyExtractor( + BodyExtractor, ReactiveHttpInputMessage> bodyExtractor) { Assert.notNull(bodyExtractor, "bodyExtractor cannot be null"); this.bodyExtractor = bodyExtractor; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java index 351f9f43b0b..4d35c39a2ff 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java @@ -17,12 +17,10 @@ package org.springframework.security.oauth2.client.endpoint; import java.time.Instant; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; -import com.nimbusds.oauth2.sdk.AccessTokenResponse; -import com.nimbusds.oauth2.sdk.TokenResponse; -import com.nimbusds.oauth2.sdk.token.AccessToken; -import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -30,7 +28,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -45,14 +42,15 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; -import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -420,77 +418,20 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { @Test public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"Token\": \"access-token-1234\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"refresh_token\": \"refresh-token-1234\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n" - + "}\n"; - // @formatter:on + String accessTokenSuccessResponse = "{}"; WebClientReactiveAuthorizationCodeTokenResponseClient customClient = new WebClientReactiveAuthorizationCodeTokenResponseClient(); - customClient.setBodyExtractor((inputMessage, context) -> { - - BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors - .toMono(new ParameterizedTypeReference>() {}); - - return delegate.extract(inputMessage, context) - .flatMap(it -> { - - return Mono.fromCallable(() -> { - - it.put("access_token", it.get("Token")); - it.put("token_type", "bearer"); - - AccessTokenResponse accessTokenResponse = - (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); - - AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); - OAuth2AccessToken.TokenType accessTokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - long expiresIn = accessToken.getLifetime(); - Set scopes = (accessToken.getScope() != null) ? - new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); - String refreshToken = null; - if (accessTokenResponse.getTokens().getRefreshToken() != null) { - refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); - } - Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); + BodyExtractor, ReactiveHttpInputMessage> extractor = mock(BodyExtractor.class); + OAuth2AccessTokenResponse response = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); + given(extractor.extract(any(), any())).willReturn(Mono.just(response)); - // @formatter:off - return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .refreshToken(refreshToken) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - - }); - - }); - - }); + customClient.setBodyExtractor(extractor); this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); - OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(authorizationCodeGrantRequest()).block(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); - assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(authorizationCodeGrantRequest()) + .block(); + assertThat(accessTokenResponse.getAccessToken()).isNotNull(); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java index 23f21eaa7ea..cc48fb828c0 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java @@ -18,13 +18,9 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.*; +import java.util.Base64; +import java.util.Collections; -import com.nimbusds.oauth2.sdk.AccessTokenResponse; -import com.nimbusds.oauth2.sdk.TokenResponse; -import com.nimbusds.oauth2.sdk.token.AccessToken; -import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -32,7 +28,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -40,11 +35,10 @@ import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; -import org.springframework.web.reactive.function.BodyExtractors; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Mono; @@ -52,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -294,79 +289,21 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { @Test public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { - // @formatter:off - enqueueJson("{\n" - + " \"Token\": \"access-token-1234\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"refresh_token\": \"refresh-token-1234\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n" - + "}\n"); - // @formatter:on + enqueueJson("{}"); WebClientReactiveClientCredentialsTokenResponseClient customClient = new WebClientReactiveClientCredentialsTokenResponseClient(); - customClient.setBodyExtractor((inputMessage, context) -> { - - BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors - .toMono(new ParameterizedTypeReference>() {}); - - return delegate.extract(inputMessage, context) - .flatMap(it -> { - - return Mono.fromCallable(() -> { - - it.put("access_token", it.get("Token")); - it.put("token_type", "bearer"); - - AccessTokenResponse accessTokenResponse = - (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); - - AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); - OAuth2AccessToken.TokenType accessTokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - long expiresIn = accessToken.getLifetime(); - Set scopes = (accessToken.getScope() != null) ? - new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); - - String refreshToken = null; - if (accessTokenResponse.getTokens().getRefreshToken() != null) { - refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); - } - Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); - - // @formatter:off - return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .refreshToken(refreshToken) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - - }); - }); + BodyExtractor, ReactiveHttpInputMessage> extractor = mock(BodyExtractor.class); + OAuth2AccessTokenResponse response = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); + given(extractor.extract(any(), any())).willReturn(Mono.just(response)); - }); + customClient.setBodyExtractor(extractor); OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest( this.clientRegistration.build()); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(request).block(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); - assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + assertThat(accessTokenResponse.getAccessToken()).isNotNull(); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java index 867103fa2d5..27a4d4ce642 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java @@ -17,12 +17,8 @@ package org.springframework.security.oauth2.client.endpoint; import java.time.Instant; -import java.util.*; +import java.util.Collections; -import com.nimbusds.oauth2.sdk.AccessTokenResponse; -import com.nimbusds.oauth2.sdk.TokenResponse; -import com.nimbusds.oauth2.sdk.token.AccessToken; -import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -30,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -42,13 +37,14 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; -import org.springframework.web.reactive.function.BodyExtractors; import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -299,81 +295,24 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { @Test public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"Token\": \"access-token-1234\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"refresh_token\": \"refresh-token-1234\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n" - + "}\n"; - // @formatter:on + String accessTokenSuccessResponse = "{}"; WebClientReactivePasswordTokenResponseClient customClient = new WebClientReactivePasswordTokenResponseClient(); - customClient.setBodyExtractor((inputMessage, context) -> { - - BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors - .toMono(new ParameterizedTypeReference>() {}); - - return delegate.extract(inputMessage, context) - .flatMap(it -> { - - return Mono.fromCallable(() -> { - - it.put("access_token", it.get("Token")); - it.put("token_type", "bearer"); - - AccessTokenResponse accessTokenResponse = - (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); - AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); - OAuth2AccessToken.TokenType accessTokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - long expiresIn = accessToken.getLifetime(); - Set scopes = (accessToken.getScope() != null) ? - new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); + BodyExtractor, ReactiveHttpInputMessage> extractor = mock(BodyExtractor.class); + OAuth2AccessTokenResponse response = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); + given(extractor.extract(any(), any())).willReturn(Mono.just(response)); - String refreshToken = null; - if (accessTokenResponse.getTokens().getRefreshToken() != null) { - refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); - } - Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); - - // @formatter:off - return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .refreshToken(refreshToken) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - - }); - - }); - - }); + customClient.setBodyExtractor(extractor); ClientRegistration clientRegistration = this.clientRegistrationBuilder.build(); OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, this.username, this.password); this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); + OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(passwordGrantRequest).block(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); - assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + assertThat(accessTokenResponse.getAccessToken()).isNotNull(); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java index f69d402ae82..c26753ff15d 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java @@ -17,12 +17,8 @@ package org.springframework.security.oauth2.client.endpoint; import java.time.Instant; -import java.util.*; +import java.util.Collections; -import com.nimbusds.oauth2.sdk.AccessTokenResponse; -import com.nimbusds.oauth2.sdk.TokenResponse; -import com.nimbusds.oauth2.sdk.token.AccessToken; -import net.minidev.json.JSONObject; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; @@ -30,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -45,13 +40,14 @@ import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; -import org.springframework.web.reactive.function.BodyExtractors; import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -302,80 +298,22 @@ public void convertWhenHeadersConverterSetThenCalled() throws Exception { @Test public void getTokenResponseWhenSuccessCustomResponseThenReturnAccessTokenResponse() { - // @formatter:off - String accessTokenSuccessResponse = "{\n" - + " \"Token\": \"access-token-1234\",\n" - + " \"expires_in\": \"3600\",\n" - + " \"scope\": \"openid profile\",\n" - + " \"refresh_token\": \"refresh-token-1234\",\n" - + " \"custom_parameter_1\": \"custom-value-1\",\n" - + " \"custom_parameter_2\": \"custom-value-2\"\n" - + "}\n"; - // @formatter:on + String accessTokenSuccessResponse = "{}"; WebClientReactiveRefreshTokenTokenResponseClient customClient = new WebClientReactiveRefreshTokenTokenResponseClient(); - customClient.setBodyExtractor((inputMessage, context) -> { - - BodyExtractor>, ReactiveHttpInputMessage> delegate = BodyExtractors - .toMono(new ParameterizedTypeReference>() {}); - - return delegate.extract(inputMessage, context) - .flatMap(it -> { - - return Mono.fromCallable(() -> { - - it.put("access_token", it.get("Token")); - it.put("token_type", "bearer"); - - AccessTokenResponse accessTokenResponse = - (AccessTokenResponse) TokenResponse.parse(new JSONObject(it)); - AccessToken accessToken = accessTokenResponse.getTokens().getAccessToken(); - OAuth2AccessToken.TokenType accessTokenType = null; - if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(accessToken.getType().getValue())) { - accessTokenType = OAuth2AccessToken.TokenType.BEARER; - } - long expiresIn = accessToken.getLifetime(); - Set scopes = (accessToken.getScope() != null) ? - new LinkedHashSet<>(accessToken.getScope().toStringList()) : Collections.emptySet(); + BodyExtractor, ReactiveHttpInputMessage> extractor = mock(BodyExtractor.class); + OAuth2AccessTokenResponse response = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); + given(extractor.extract(any(), any())).willReturn(Mono.just(response)); - String refreshToken = null; - if (accessTokenResponse.getTokens().getRefreshToken() != null) { - refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue(); - } - Map additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters()); - - // @formatter:off - return OAuth2AccessTokenResponse.withToken(accessToken.getValue()) - .tokenType(accessTokenType) - .expiresIn(expiresIn) - .scopes(scopes) - .refreshToken(refreshToken) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - - }); - - }); - - }); + customClient.setBodyExtractor(extractor); OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest( this.clientRegistrationBuilder.build(), this.accessToken, this.refreshToken); this.server.enqueue(jsonResponse(accessTokenSuccessResponse)); - Instant expiresAtBefore = Instant.now().plusSeconds(3600); OAuth2AccessTokenResponse accessTokenResponse = customClient.getTokenResponse(refreshTokenGrantRequest).block(); - Instant expiresAtAfter = Instant.now().plusSeconds(3600); - assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); - assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); - assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(expiresAtBefore, expiresAtAfter); - assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("openid", "profile"); - assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); - assertThat(accessTokenResponse.getAdditionalParameters().size()).isEqualTo(2); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_1", "custom-value-1"); - assertThat(accessTokenResponse.getAdditionalParameters()).containsEntry("custom_parameter_2", "custom-value-2"); + assertThat(accessTokenResponse.getAccessToken()).isNotNull(); } From 49d0007a70ac43c255f5801a5c6f924761816ff9 Mon Sep 17 00:00:00 2001 From: bishoy basily Date: Thu, 16 Sep 2021 09:42:21 +0200 Subject: [PATCH 4/4] all CheckStyle errors fixed Closes gh-10260 --- ...ClientReactiveAuthorizationCodeTokenResponseClientTests.java | 2 +- ...ClientReactiveClientCredentialsTokenResponseClientTests.java | 2 +- .../WebClientReactivePasswordTokenResponseClientTests.java | 2 +- .../WebClientReactiveRefreshTokenTokenResponseClientTests.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java index 4d35c39a2ff..d2cc7bb0e96 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClientTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; @@ -45,7 +46,6 @@ import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java index cc48fb828c0..a2a3fc0f7fe 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClientTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; @@ -41,7 +42,6 @@ import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; -import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java index 27a4d4ce642..5b0118ebf9f 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClientTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; @@ -39,7 +40,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; -import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java index c26753ff15d..01f36f0fb86 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClientTests.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; @@ -42,7 +43,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; import org.springframework.web.reactive.function.BodyExtractor; -import reactor.core.publisher.Mono; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;