From 18f48e4a1618ff05379f7f41478096b36f5cddd0 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 27 Nov 2019 12:11:14 -0500 Subject: [PATCH 001/348] DefaultReactiveOAuth2AuthorizedClientManager requires non-null serverWebExchange Issue gh-7544 --- ...ReactiveOAuth2AuthorizedClientManager.java | 5 +- ...iveOAuth2AuthorizedClientManagerTests.java | 47 +++++++------------ 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java index 5e64d7ba7e..852e910e6f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManager.java @@ -99,15 +99,16 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React private Mono loadAuthorizedClient(String clientRegistrationId, Authentication principal, ServerWebExchange serverWebExchange) { return Mono.justOrEmpty(serverWebExchange) .switchIfEmpty(Mono.defer(() -> currentServerWebExchange())) + .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("serverWebExchange cannot be null"))) .flatMap(exchange -> this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, exchange)); } private Mono saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal, ServerWebExchange serverWebExchange) { return Mono.justOrEmpty(serverWebExchange) .switchIfEmpty(Mono.defer(() -> currentServerWebExchange())) + .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("serverWebExchange cannot be null"))) .flatMap(exchange -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, exchange) - .thenReturn(authorizedClient)) - .defaultIfEmpty(authorizedClient); + .thenReturn(authorizedClient)); } private static Mono currentServerWebExchange() { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java index 2dd4e3ed6a..1e1bcbb3bc 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/DefaultReactiveOAuth2AuthorizedClientManagerTests.java @@ -65,6 +65,7 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests { private MockServerWebExchange serverWebExchange; private Context context; private ArgumentCaptor authorizationContextCaptor; + private PublisherProbe loadAuthorizedClientProbe; private PublisherProbe saveAuthorizedClientProbe; @SuppressWarnings("unchecked") @@ -74,8 +75,9 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests { when(this.clientRegistrationRepository.findByRegistrationId( anyString())).thenReturn(Mono.empty()); this.authorizedClientRepository = mock(ServerOAuth2AuthorizedClientRepository.class); + this.loadAuthorizedClientProbe = PublisherProbe.empty(); when(this.authorizedClientRepository.loadAuthorizedClient( - anyString(), any(Authentication.class), any(ServerWebExchange.class))).thenReturn(Mono.empty()); + anyString(), any(Authentication.class), any(ServerWebExchange.class))).thenReturn(this.loadAuthorizedClientProbe.mono()); this.saveAuthorizedClientProbe = PublisherProbe.empty(); when(this.authorizedClientRepository.saveAuthorizedClient( any(OAuth2AuthorizedClient.class), any(Authentication.class), any(ServerWebExchange.class))).thenReturn(this.saveAuthorizedClientProbe.mono()); @@ -131,6 +133,16 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests { .hasMessage("authorizeRequest cannot be null"); } + @Test + public void authorizeWhenExchangeIsNullThenThrowIllegalArgumentException() { + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(this.clientRegistration.getRegistrationId()) + .principal(this.principal) + .build(); + assertThatThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("serverWebExchange cannot be null"); + } + @Test public void authorizeWhenClientRegistrationNotFoundThenThrowIllegalArgumentException() { OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId("invalid-registration-id") @@ -162,7 +174,8 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests { assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); assertThat(authorizedClient).isNull(); - verify(this.authorizedClientRepository, never()).saveAuthorizedClient(any(), any(), any()); + this.loadAuthorizedClientProbe.assertWasSubscribed(); + this.saveAuthorizedClientProbe.assertWasNotSubscribed(); } @SuppressWarnings("unchecked") @@ -193,38 +206,14 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests { this.saveAuthorizedClientProbe.assertWasSubscribed(); } - @Test - public void authorizeWhenNotAuthorizedAndSupportedProviderAndExchangeUnavailableThenAuthorizedButNotSaved() { - when(this.clientRegistrationRepository.findByRegistrationId( - eq(this.clientRegistration.getRegistrationId()))).thenReturn(Mono.just(this.clientRegistration)); - - when(this.authorizedClientProvider.authorize( - any(OAuth2AuthorizationContext.class))).thenReturn(Mono.just(this.authorizedClient)); - - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(this.clientRegistration.getRegistrationId()) - .principal(this.principal) - .build(); - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - - verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); - verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); - - OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); - assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); - assertThat(authorizationContext.getAuthorizedClient()).isNull(); - assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); - - assertThat(authorizedClient).isSameAs(this.authorizedClient); - verify(this.authorizedClientRepository, never()).saveAuthorizedClient(any(), any(), any()); - } - @SuppressWarnings("unchecked") @Test public void authorizeWhenAuthorizedAndSupportedProviderThenReauthorized() { when(this.clientRegistrationRepository.findByRegistrationId( eq(this.clientRegistration.getRegistrationId()))).thenReturn(Mono.just(this.clientRegistration)); + this.loadAuthorizedClientProbe = PublisherProbe.of(Mono.just(this.authorizedClient)); when(this.authorizedClientRepository.loadAuthorizedClient( - eq(this.clientRegistration.getRegistrationId()), eq(this.principal), eq(this.serverWebExchange))).thenReturn(Mono.just(this.authorizedClient)); + eq(this.clientRegistration.getRegistrationId()), eq(this.principal), eq(this.serverWebExchange))).thenReturn(this.loadAuthorizedClientProbe.mono()); OAuth2AuthorizedClient reauthorizedClient = new OAuth2AuthorizedClient( this.clientRegistration, this.principal.getName(), @@ -313,7 +302,7 @@ public class DefaultReactiveOAuth2AuthorizedClientManagerTests { assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); assertThat(authorizedClient).isSameAs(this.authorizedClient); - verify(this.authorizedClientRepository, never()).saveAuthorizedClient(any(), any(), any()); + this.saveAuthorizedClientProbe.assertWasNotSubscribed(); } @SuppressWarnings("unchecked") From 19c2209a12c52eec87c7ae67a6b2d455e2ab8fba Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 27 Nov 2019 16:10:48 -0500 Subject: [PATCH 002/348] ServerOAuth2AuthorizedClientExchangeFilterFunction works with UnAuthenticatedServerOAuth2AuthorizedClientRepository Fixes gh-7544 --- ...uthorizedClientExchangeFilterFunction.java | 67 ++++++++++++++++++- ...izedClientExchangeFilterFunctionTests.java | 38 +++++++++++ 2 files changed, 104 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java index 33a878172b..512083f9f2 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java @@ -22,6 +22,7 @@ import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.ReactiveSecurityContextHolder; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; @@ -35,6 +36,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.UnAuthenticatedServerOAuth2AuthorizedClientRepository; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientResponse; @@ -124,6 +126,17 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements .clientCredentials() .password() .build(); + + // gh-7544 + if (authorizedClientRepository instanceof UnAuthenticatedServerOAuth2AuthorizedClientRepository) { + UnAuthenticatedReactiveOAuth2AuthorizedClientManager unauthenticatedAuthorizedClientManager = + new UnAuthenticatedReactiveOAuth2AuthorizedClientManager( + clientRegistrationRepository, + (UnAuthenticatedServerOAuth2AuthorizedClientRepository) authorizedClientRepository); + unauthenticatedAuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + return unauthenticatedAuthorizedClientManager; + } + DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( clientRegistrationRepository, authorizedClientRepository); authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); @@ -266,7 +279,11 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements .clientCredentials(this::updateClientCredentialsProvider) .password(configurer -> configurer.clockSkew(this.accessTokenExpiresSkew)) .build(); - ((DefaultReactiveOAuth2AuthorizedClientManager) this.authorizedClientManager).setAuthorizedClientProvider(authorizedClientProvider); + if (this.authorizedClientManager instanceof UnAuthenticatedReactiveOAuth2AuthorizedClientManager) { + ((UnAuthenticatedReactiveOAuth2AuthorizedClientManager) this.authorizedClientManager).setAuthorizedClientProvider(authorizedClientProvider); + } else { + ((DefaultReactiveOAuth2AuthorizedClientManager) this.authorizedClientManager).setAuthorizedClientProvider(authorizedClientProvider); + } } private void updateClientCredentialsProvider(ReactiveOAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilder builder) { @@ -376,4 +393,52 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements .headers(headers -> headers.setBearerAuth(authorizedClient.getAccessToken().getTokenValue())) .build(); } + + private static class UnAuthenticatedReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager { + private final ReactiveClientRegistrationRepository clientRegistrationRepository; + private final UnAuthenticatedServerOAuth2AuthorizedClientRepository authorizedClientRepository; + private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider; + + private UnAuthenticatedReactiveOAuth2AuthorizedClientManager( + ReactiveClientRegistrationRepository clientRegistrationRepository, + UnAuthenticatedServerOAuth2AuthorizedClientRepository authorizedClientRepository) { + this.clientRegistrationRepository = clientRegistrationRepository; + this.authorizedClientRepository = authorizedClientRepository; + } + + @Override + public Mono authorize(OAuth2AuthorizeRequest authorizeRequest) { + Assert.notNull(authorizeRequest, "authorizeRequest cannot be null"); + + String clientRegistrationId = authorizeRequest.getClientRegistrationId(); + Authentication principal = authorizeRequest.getPrincipal(); + + return Mono.justOrEmpty(authorizeRequest.getAuthorizedClient()) + .switchIfEmpty(Mono.defer(() -> this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, null))) + .flatMap(authorizedClient -> { + // Re-authorize + return Mono.just(OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient).principal(principal).build()) + .flatMap(this.authorizedClientProvider::authorize) + .flatMap(reauthorizedClient -> this.authorizedClientRepository.saveAuthorizedClient(reauthorizedClient, principal, null).thenReturn(reauthorizedClient)) + // Default to the existing authorizedClient if the client was not re-authorized + .defaultIfEmpty(authorizeRequest.getAuthorizedClient() != null ? + authorizeRequest.getAuthorizedClient() : authorizedClient); + }) + .switchIfEmpty(Mono.deferWithContext(context -> + // Authorize + this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId) + .switchIfEmpty(Mono.error(() -> new IllegalArgumentException( + "Could not find ClientRegistration with id '" + clientRegistrationId + "'"))) + .flatMap(clientRegistration -> Mono.just(OAuth2AuthorizationContext.withClientRegistration(clientRegistration).principal(principal).build())) + .flatMap(this.authorizedClientProvider::authorize) + .flatMap(authorizedClient -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, null).thenReturn(authorizedClient)) + .subscriberContext(context) + )); + } + + private void setAuthorizedClientProvider(ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider) { + Assert.notNull(authorizedClientProvider, "authorizedClientProvider cannot be null"); + this.authorizedClientProvider = authorizedClientProvider; + } + } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java index abffbe7d58..4156a34670 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunctionTests.java @@ -57,6 +57,7 @@ import org.springframework.security.oauth2.client.registration.ReactiveClientReg import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.UnAuthenticatedServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; @@ -587,6 +588,43 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionTests { verify(this.authorizedClientRepository).loadAuthorizedClient(eq(this.registration.getRegistrationId()), any(), eq(this.serverWebExchange)); } + // gh-7544 + @Test + public void filterWhenClientCredentialsClientNotAuthorizedAndOutsideRequestContextThenGetNewToken() { + // Use UnAuthenticatedServerOAuth2AuthorizedClientRepository when operating outside of a request context + ServerOAuth2AuthorizedClientRepository unauthenticatedAuthorizedClientRepository = spy(new UnAuthenticatedServerOAuth2AuthorizedClientRepository()); + this.function = new ServerOAuth2AuthorizedClientExchangeFilterFunction( + this.clientRegistrationRepository, unauthenticatedAuthorizedClientRepository); + this.function.setClientCredentialsTokenResponseClient(this.clientCredentialsTokenResponseClient); + + OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("new-token") + .tokenType(OAuth2AccessToken.TokenType.BEARER) + .expiresIn(360) + .build(); + when(this.clientCredentialsTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse)); + + ClientRegistration registration = TestClientRegistrations.clientCredentials().build(); + when(this.clientRegistrationRepository.findByRegistrationId(eq(registration.getRegistrationId()))).thenReturn(Mono.just(registration)); + + ClientRequest request = ClientRequest.create(GET, URI.create("https://example.com")) + .attributes(clientRegistrationId(registration.getRegistrationId())) + .build(); + + this.function.filter(request, this.exchange).block(); + + verify(unauthenticatedAuthorizedClientRepository).loadAuthorizedClient(any(), any(), any()); + verify(this.clientCredentialsTokenResponseClient).getTokenResponse(any()); + verify(unauthenticatedAuthorizedClientRepository).saveAuthorizedClient(any(), any(), any()); + + List requests = this.exchange.getRequests(); + assertThat(requests).hasSize(1); + ClientRequest request1 = requests.get(0); + assertThat(request1.headers().getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer new-token"); + assertThat(request1.url().toASCIIString()).isEqualTo("https://example.com"); + assertThat(request1.method()).isEqualTo(HttpMethod.GET); + assertThat(getBody(request1)).isEmpty(); + } + private Context serverWebExchange() { return Context.of(ServerWebExchange.class, this.serverWebExchange); } From b905cb8aaa98b4d702af34ae67c18dfec2582a9e Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 27 Nov 2019 12:37:47 -0500 Subject: [PATCH 003/348] Polish OAuth2AuthorizedClientArgumentResolver --- .../OAuth2AuthorizedClientArgumentResolver.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java index 38e825b3f7..5f784a51c1 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/result/method/annotation/OAuth2AuthorizedClientArgumentResolver.java @@ -137,13 +137,11 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth .switchIfEmpty(currentServerWebExchange()); return Mono.zip(defaultedRegistrationId, defaultedAuthentication, defaultedExchange) - .map(t3 -> { - OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withClientRegistrationId(t3.getT1()).principal(t3.getT2()); - if (t3.getT3() != null) { - builder.attribute(ServerWebExchange.class.getName(), t3.getT3()); - } - return builder.build(); - }); + .map(t3 -> OAuth2AuthorizeRequest.withClientRegistrationId(t3.getT1()) + .principal(t3.getT2()) + .attribute(ServerWebExchange.class.getName(), t3.getT3()) + .build() + ); } private Mono currentAuthentication() { From 0babe7d930b6c2aaef9d4b9ad0f65295c3493881 Mon Sep 17 00:00:00 2001 From: Alexey Nesterov Date: Tue, 26 Nov 2019 10:14:25 +0000 Subject: [PATCH 004/348] Correctly configure authorization requests repository for OAuth2 login To use custom ServerAuthorizationRequestRepository both OAuth2AuthorizationRequestRedirectWebFilter and OAuth2LoginAuthenticationWebFilter should use the same repo provided in the configuration. Currently the former filter is correctly configured, but the latter always uses default, WebSession based repository. So authorization code created before redirect to authorization endpoint will never be found to complete OAuth2 login when custom ServerAuthorizationRequestRepository is used. This change also makes OAuth2Client and OAuth2Login authentication converters consistent. Fixes gh-7675 --- .../config/web/server/ServerHttpSecurity.java | 4 ++- .../web/server/ServerHttpSecurityTests.java | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 19711e54af..20eb5ab45d 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -1084,7 +1084,9 @@ public class ServerHttpSecurity { private ServerAuthenticationConverter getAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) { if (this.authenticationConverter == null) { - this.authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository); + ServerOAuth2AuthorizationCodeAuthenticationTokenConverter authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository); + authenticationConverter.setAuthorizationRequestRepository(getAuthorizationRequestRepository()); + this.authenticationConverter = authenticationConverter; } return this.authenticationConverter; } diff --git a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java index c95f8bd17d..c84e79e64a 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java @@ -39,6 +39,10 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter; import reactor.core.publisher.Mono; @@ -475,6 +479,28 @@ public class ServerHttpSecurityTests { verify(customServerCsrfTokenRepository).loadToken(any()); } + @Test + public void shouldConfigureAuthorizationRequestRepositoryForOAuth2Login() { + ServerAuthorizationRequestRepository authorizationRequestRepository = mock(ServerAuthorizationRequestRepository.class); + ReactiveClientRegistrationRepository clientRegistrationRepository = mock(ReactiveClientRegistrationRepository.class); + + OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request().build(); + + when(authorizationRequestRepository.removeAuthorizationRequest(any())).thenReturn(Mono.just(authorizationRequest)); + + SecurityWebFilterChain securityFilterChain = this.http + .oauth2Login() + .clientRegistrationRepository(clientRegistrationRepository) + .authorizationRequestRepository(authorizationRequestRepository) + .and() + .build(); + + WebTestClient client = WebTestClientBuilder.bindToWebFilters(securityFilterChain).build(); + client.get().uri("/login/oauth2/code/registration-id").exchange(); + + verify(authorizationRequestRepository).removeAuthorizationRequest(any()); + } + private boolean isX509Filter(WebFilter filter) { try { Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter"); From e4aa3be4c5bd6b0f8b046e7f4265139f39240e21 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 5 Dec 2019 16:50:43 -0500 Subject: [PATCH 005/348] WebFlux oauth2Login() redirects on failed authentication Fixes gh-5562 gh-6484 --- .../config/web/server/ServerHttpSecurity.java | 12 ++- .../config/web/server/OAuth2LoginTests.java | 80 +++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 20eb5ab45d..cb719cd448 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -986,7 +986,7 @@ public class ServerHttpSecurity { private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler(); - private ServerAuthenticationFailureHandler authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception); + private ServerAuthenticationFailureHandler authenticationFailureHandler; /** * Configures the {@link ReactiveAuthenticationManager} to use. The default is @@ -1028,6 +1028,7 @@ public class ServerHttpSecurity { /** * The {@link ServerAuthenticationFailureHandler} used after authentication failure. + * Defaults to {@link RedirectServerAuthenticationFailureHandler} redirecting to "/login?error". * * @since 5.2 * @param authenticationFailureHandler the failure handler to use @@ -1175,7 +1176,7 @@ public class ServerHttpSecurity { authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository)); authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler); - authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler); + authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler()); authenticationFilter.setSecurityContextRepository(this.securityContextRepository); MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher( @@ -1192,6 +1193,13 @@ public class ServerHttpSecurity { http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION); } + private ServerAuthenticationFailureHandler getAuthenticationFailureHandler() { + if (this.authenticationFailureHandler == null) { + this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler("/login?error"); + } + return this.authenticationFailureHandler; + } + private ServerWebExchangeMatcher createAttemptAuthenticationRequestMatcher() { return new PathPatternParserServerWebExchangeMatcher("/login/oauth2/code/{registrationId}"); } diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index 046675934b..a783a8212a 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -70,6 +70,7 @@ import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.TestOAuth2Users; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtValidationException; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; @@ -518,6 +519,85 @@ public class OAuth2LoginTests { verify(securityContextRepository).save(any(), any()); } + // gh-5562 + @Test + public void oauth2LoginWhenAccessTokenRequestFailsThenDefaultRedirectToLogin() { + this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, + OAuth2LoginWithCustomBeansConfig.class).autowire(); + + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(this.springSecurity) + .build(); + + OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build(); + OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); + OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); + OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); + OAuth2AuthorizationCodeAuthenticationToken authenticationToken = new OAuth2AuthorizationCodeAuthenticationToken(google, exchange, accessToken); + + OAuth2LoginWithCustomBeansConfig config = this.spring.getContext().getBean(OAuth2LoginWithCustomBeansConfig.class); + + ServerAuthenticationConverter converter = config.authenticationConverter; + when(converter.convert(any())).thenReturn(Mono.just(authenticationToken)); + + ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; + OAuth2Error oauth2Error = new OAuth2Error("invalid_request", "Invalid request", null); + when(tokenResponseClient.getTokenResponse(any())).thenThrow(new OAuth2AuthenticationException(oauth2Error)); + + webTestClient.get() + .uri("/login/oauth2/code/google") + .exchange() + .expectStatus() + .is3xxRedirection() + .expectHeader() + .valueEquals("Location", "/login?error"); + } + + // gh-6484 + @Test + public void oauth2LoginWhenIdTokenValidationFailsThenDefaultRedirectToLogin() { + this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, + OAuth2LoginWithCustomBeansConfig.class).autowire(); + + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(this.springSecurity) + .build(); + + OAuth2LoginWithCustomBeansConfig config = this.spring.getContext().getBean(OAuth2LoginWithCustomBeansConfig.class); + + OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build(); + OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); + OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); + OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); + OAuth2AuthorizationCodeAuthenticationToken authenticationToken = new OAuth2AuthorizationCodeAuthenticationToken(google, exchange, accessToken); + + ServerAuthenticationConverter converter = config.authenticationConverter; + when(converter.convert(any())).thenReturn(Mono.just(authenticationToken)); + + Map additionalParameters = new HashMap<>(); + additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); + OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) + .tokenType(accessToken.getTokenType()) + .scopes(accessToken.getScopes()) + .additionalParameters(additionalParameters) + .build(); + ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; + when(tokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse)); + + ReactiveJwtDecoderFactory jwtDecoderFactory = config.jwtDecoderFactory; + OAuth2Error oauth2Error = new OAuth2Error("invalid_id_token", "Invalid ID Token", null); + when(jwtDecoderFactory.createDecoder(any())).thenReturn(token -> + Mono.error(new JwtValidationException("ID Token validation failed", Collections.singleton(oauth2Error)))); + + webTestClient.get() + .uri("/login/oauth2/code/google") + .exchange() + .expectStatus() + .is3xxRedirection() + .expectHeader() + .valueEquals("Location", "/login?error"); + } + @Configuration static class OAuth2LoginWithCustomBeansConfig { From 752d5f29aa72874bd5097a343853b4b1c2e1ff4f Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 5 Dec 2019 16:54:31 -0500 Subject: [PATCH 006/348] Display general error message when WebFlux oauth2Login() fails Issue gh-5562 gh-6484 --- .../web/server/ui/LoginPageGeneratingWebFilter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java index 2c760654c2..5daa3f0012 100644 --- a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java @@ -106,7 +106,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter { + " \n" + "
\n" + formLogin(queryParams, csrfTokenHtmlInput) - + oauth2LoginLinks(contextPath, this.oauth2AuthenticationUrlToClientName) + + oauth2LoginLinks(queryParams, contextPath, this.oauth2AuthenticationUrlToClientName) + "
\n" + " \n" + ""; @@ -135,12 +135,14 @@ public class LoginPageGeneratingWebFilter implements WebFilter { + " \n"; } - private static String oauth2LoginLinks(String contextPath, Map oauth2AuthenticationUrlToClientName) { + private static String oauth2LoginLinks(MultiValueMap queryParams, String contextPath, Map oauth2AuthenticationUrlToClientName) { if (oauth2AuthenticationUrlToClientName.isEmpty()) { return ""; } + boolean isError = queryParams.containsKey("error"); StringBuilder sb = new StringBuilder(); sb.append("
"); + sb.append(createError(isError)); sb.append("\n"); for (Map.Entry clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) { sb.append("
"); From 148b570a987316269a692d4a910fd7593bc1bccc Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Fri, 6 Dec 2019 11:16:10 -0500 Subject: [PATCH 007/348] Remove redundant validation for redirect-uri Fixes gh-7706 --- .../OAuth2AuthorizationExchangeValidator.java | 8 +---- ...thorizationCodeAuthenticationProvider.java | 6 ---- ...tionCodeReactiveAuthenticationManager.java | 6 ---- ...zationCodeAuthenticationProviderTests.java | 15 +------- ...odeReactiveAuthenticationManagerTests.java | 9 +---- ...Auth2LoginAuthenticationProviderTests.java | 16 +-------- ...zationCodeAuthenticationProviderTests.java | 13 ------- .../samples/OAuth2LoginApplicationTests.java | 36 ------------------- 8 files changed, 4 insertions(+), 105 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java index 37d6d8115b..a240e0521a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -30,7 +30,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp */ final class OAuth2AuthorizationExchangeValidator { private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; - private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; static void validate(OAuth2AuthorizationExchange authorizationExchange) { OAuth2AuthorizationRequest authorizationRequest = authorizationExchange.getAuthorizationRequest(); @@ -44,10 +43,5 @@ final class OAuth2AuthorizationExchangeValidator { OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); throw new OAuth2AuthorizationException(oauth2Error); } - - if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE); - throw new OAuth2AuthorizationException(oauth2Error); - } } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java index 07490b6277..9246502d74 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java @@ -78,7 +78,6 @@ import java.util.Map; */ public class OidcAuthorizationCodeAuthenticationProvider implements AuthenticationProvider { private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; - private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token"; private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce"; private final OAuth2AccessTokenResponseClient accessTokenResponseClient; @@ -132,11 +131,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE); - throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); - } - OAuth2AccessTokenResponse accessTokenResponse; try { accessTokenResponse = this.accessTokenResponseClient.getTokenResponse( diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java index e4612df291..9852ff1479 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java @@ -80,7 +80,6 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager { private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; - private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter"; private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token"; private static final String INVALID_NONCE_ERROR_CODE = "invalid_nonce"; @@ -131,11 +130,6 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE); - throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); - } - OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest( authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange()); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java index 4f7157afcc..2ba5e36927 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -98,19 +98,6 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests { }).isInstanceOf(OAuth2AuthorizationException.class).hasMessageContaining("invalid_state_parameter"); } - @Test - public void authenticateWhenAuthorizationResponseRedirectUriNotEqualAuthorizationRequestRedirectUriThenThrowOAuth2AuthorizationException() { - OAuth2AuthorizationResponse authorizationResponse = success().redirectUri("https://example2.com").build(); - OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange( - this.authorizationRequest, authorizationResponse); - - assertThatThrownBy(() -> { - this.authenticationProvider.authenticate( - new OAuth2AuthorizationCodeAuthenticationToken( - this.clientRegistration, authorizationExchange)); - }).isInstanceOf(OAuth2AuthorizationException.class).hasMessageContaining("invalid_redirect_uri_parameter"); - } - @Test public void authenticateWhenAuthorizationSuccessResponseThenExchangedForAccessToken() { OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponse().refreshToken("refresh").build(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManagerTests.java index 1d5f0cd15b..f375342212 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManagerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -81,13 +81,6 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManagerTests { .isInstanceOf(OAuth2AuthorizationException.class); } - @Test - public void authenticateWhenRedirectUriNotEqualThenOAuth2AuthorizationException() { - this.authorizationRequest.redirectUri("https://example.org/notequal"); - assertThatCode(() -> authenticate()) - .isInstanceOf(OAuth2AuthorizationException.class); - } - @Test public void authenticateWhenValidThenSuccess() { when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(this.tokenResponse.build())); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java index e4431ee471..8a2aaa10d1 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -151,20 +151,6 @@ public class OAuth2LoginAuthenticationProviderTests { new OAuth2LoginAuthenticationToken(this.clientRegistration, authorizationExchange)); } - @Test - public void authenticateWhenAuthorizationResponseRedirectUriNotEqualAuthorizationRequestRedirectUriThenThrowOAuth2AuthenticationException() { - this.exception.expect(OAuth2AuthenticationException.class); - this.exception.expectMessage(containsString("invalid_redirect_uri_parameter")); - - OAuth2AuthorizationResponse authorizationResponse = - success().redirectUri("https://example2.com").build(); - OAuth2AuthorizationExchange authorizationExchange = - new OAuth2AuthorizationExchange(this.authorizationRequest, authorizationResponse); - - this.authenticationProvider.authenticate( - new OAuth2LoginAuthenticationToken(this.clientRegistration, authorizationExchange)); - } - @Test public void authenticateWhenLoginSuccessThenReturnAuthentication() { OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenSuccessResponse(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java index ded67aa501..4c9fc9acbe 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProviderTests.java @@ -186,19 +186,6 @@ public class OidcAuthorizationCodeAuthenticationProviderTests { new OAuth2LoginAuthenticationToken(this.clientRegistration, authorizationExchange)); } - @Test - public void authenticateWhenAuthorizationResponseRedirectUriNotEqualAuthorizationRequestRedirectUriThenThrowOAuth2AuthenticationException() { - this.exception.expect(OAuth2AuthenticationException.class); - this.exception.expectMessage(containsString("invalid_redirect_uri_parameter")); - - OAuth2AuthorizationResponse authorizationResponse = success().redirectUri("https://example2.com").build(); - OAuth2AuthorizationExchange authorizationExchange = - new OAuth2AuthorizationExchange(this.authorizationRequest, authorizationResponse); - - this.authenticationProvider.authenticate( - new OAuth2LoginAuthenticationToken(this.clientRegistration, authorizationExchange)); - } - @Test public void authenticateWhenTokenResponseDoesNotContainIdTokenThenThrowOAuth2AuthenticationException() { this.exception.expect(OAuth2AuthenticationException.class); diff --git a/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java b/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java index 067bed4431..a395e8eb0d 100644 --- a/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java +++ b/samples/boot/oauth2login/src/integration-test/java/org/springframework/security/samples/OAuth2LoginApplicationTests.java @@ -255,42 +255,6 @@ public class OAuth2LoginApplicationTests { assertThat(errorElement.asText()).contains("authorization_request_not_found"); } - @Test - public void requestAuthorizationCodeGrantWhenInvalidRedirectUriThenDisplayLoginPageWithError() throws Exception { - HtmlPage page = this.webClient.getPage("/"); - URL loginPageUrl = page.getBaseURL(); - URL loginErrorPageUrl = new URL(loginPageUrl.toString() + "?error"); - - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - - HtmlAnchor clientAnchorElement = this.getClientAnchorElement(page, clientRegistration); - assertThat(clientAnchorElement).isNotNull(); - - WebResponse response = this.followLinkDisableRedirects(clientAnchorElement); - - UriComponents authorizeRequestUriComponents = UriComponentsBuilder.fromUri( - URI.create(response.getResponseHeaderValue("Location"))).build(); - - Map params = authorizeRequestUriComponents.getQueryParams().toSingleValueMap(); - String code = "auth-code"; - String state = URLDecoder.decode(params.get(OAuth2ParameterNames.STATE), "UTF-8"); - String redirectUri = URLDecoder.decode(params.get(OAuth2ParameterNames.REDIRECT_URI), "UTF-8"); - redirectUri += "-invalid"; - - String authorizationResponseUri = - UriComponentsBuilder.fromHttpUrl(redirectUri) - .queryParam(OAuth2ParameterNames.CODE, code) - .queryParam(OAuth2ParameterNames.STATE, state) - .build().encode().toUriString(); - - page = this.webClient.getPage(new URL(authorizationResponseUri)); - assertThat(page.getBaseURL()).isEqualTo(loginErrorPageUrl); - - HtmlElement errorElement = page.getBody().getFirstByXPath("div"); - assertThat(errorElement).isNotNull(); - assertThat(errorElement.asText()).contains("invalid_redirect_uri_parameter"); - } - private void assertLoginPage(HtmlPage page) { assertThat(page.getTitleText()).isEqualTo("Please sign in"); From 4c5c4f6cce427f88573bf57700d891181f3ce475 Mon Sep 17 00:00:00 2001 From: Ankur Pathak Date: Thu, 31 Oct 2019 19:21:39 +0530 Subject: [PATCH 008/348] Reactive Implementation of AuthorizedClientServiceOAuth2AuthorizedClientManager ReactiveOAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager is reactive version of AuthorizedClientServiceOAuth2AuthorizedClientManager Fixes: gh-7569 --- ...ReactiveOAuth2AuthorizedClientManager.java | 147 +++++++++ ...iveOAuth2AuthorizedClientManagerTests.java | 300 ++++++++++++++++++ 2 files changed, 447 insertions(+) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java new file mode 100644 index 0000000000..38bf53487c --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java @@ -0,0 +1,147 @@ +/* + * Copyright 2002-2019 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.client; + +import org.springframework.lang.Nullable; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +/** + * An implementation of an {@link ReactiveOAuth2AuthorizedClientManager} + * that is capable of operating outside of a {@code ServerHttpRequest} context, + * e.g. in a scheduled/background thread and/or in the service-tier. + * + * @author Ankur Pathak + * @see ReactiveOAuth2AuthorizedClientManager + * @see ReactiveOAuth2AuthorizedClientProvider + * @see ReactiveOAuth2AuthorizedClientService + * @since 5.3 + */ +public final class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager { + private final ReactiveClientRegistrationRepository clientRegistrationRepository; + private final ReactiveOAuth2AuthorizedClientService authorizedClientService; + private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty(); + private Function>> contextAttributesMapper = new DefaultContextAttributesMapper(); + + /** + * Constructs an {@code OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters. + * + * @param clientRegistrationRepository the repository of client registrations + * @param authorizedClientService the authorized client service + */ + public OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(ReactiveClientRegistrationRepository clientRegistrationRepository, + ReactiveOAuth2AuthorizedClientService authorizedClientService) { + Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); + Assert.notNull(authorizedClientService, "authorizedClientService cannot be null"); + this.clientRegistrationRepository = clientRegistrationRepository; + this.authorizedClientService = authorizedClientService; + } + + @Nullable + @Override + public Mono authorize(OAuth2AuthorizeRequest authorizeRequest) { + Assert.notNull(authorizeRequest, "authorizeRequest cannot be null"); + String clientRegistrationId = authorizeRequest.getClientRegistrationId(); + OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient(); + Authentication principal = authorizeRequest.getPrincipal(); + // @formatter:off + return Mono.justOrEmpty(authorizedClient) + .map(OAuth2AuthorizationContext::withAuthorizedClient) + .switchIfEmpty(Mono.defer(() -> this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId) + .flatMap(clientRegistration -> this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName()) + .map(OAuth2AuthorizationContext::withAuthorizedClient) + .switchIfEmpty(Mono.fromSupplier(() -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration))) + ) + .switchIfEmpty(Mono.error(new IllegalArgumentException("Could not find ClientRegistration with id '" + clientRegistrationId + "'"))) + ) + ) + .flatMap(contextBuilder -> this.contextAttributesMapper.apply(authorizeRequest) + .filter(contextAttributes-> !CollectionUtils.isEmpty(contextAttributes)) + .map(contextAttributes -> contextBuilder.principal(principal) + .attributes(attributes -> { + attributes.putAll(contextAttributes); + }).build()) + ).flatMap(authorizationContext -> this.authorizedClientProvider.authorize(authorizationContext) + .doOnNext(_authorizedClient -> authorizedClientService.saveAuthorizedClient(_authorizedClient, principal)) + .switchIfEmpty(Mono.defer(()-> Mono.justOrEmpty(Optional.ofNullable(authorizationContext.getAuthorizedClient())))) + ); + // @formatter:on + } + + /** + * Sets the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client. + * + * @param authorizedClientProvider the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client + */ + public void setAuthorizedClientProvider(ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider) { + Assert.notNull(authorizedClientProvider, "authorizedClientProvider cannot be null"); + this.authorizedClientProvider = authorizedClientProvider; + } + + /** + * Sets the {@code Function} used for mapping attribute(s) from the {@link OAuth2AuthorizeRequest} to a {@code Map} of attributes + * to be associated to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}. + * + * @param contextAttributesMapper the {@code Function} used for supplying the {@code Map} of attributes + * to the {@link OAuth2AuthorizationContext#getAttributes() authorization context} + */ + public void setContextAttributesMapper(Function>> contextAttributesMapper) { + Assert.notNull(contextAttributesMapper, "contextAttributesMapper cannot be null"); + this.contextAttributesMapper = contextAttributesMapper; + } + + private static Mono currentServerWebExchange() { + return Mono.subscriberContext() + .filter(c -> c.hasKey(ServerWebExchange.class)) + .map(c -> c.get(ServerWebExchange.class)); + } + + /** + * The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}. + */ + public static class DefaultContextAttributesMapper implements Function>> { + + @Override + public Mono> apply(OAuth2AuthorizeRequest authorizeRequest) { + ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName()); + return Mono.justOrEmpty(serverWebExchange) + .switchIfEmpty(Mono.defer(() -> currentServerWebExchange())) + .flatMap(exchange -> { + Map contextAttributes = Collections.emptyMap(); + String scope = exchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.SCOPE); + if (StringUtils.hasText(scope)) { + contextAttributes = new HashMap<>(); + contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME, + StringUtils.delimitedListToStringArray(scope, " ")); + } + return Mono.just(contextAttributes); + }) + .defaultIfEmpty(Collections.emptyMap()); + } + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java new file mode 100644 index 0000000000..2be9e4139d --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java @@ -0,0 +1,300 @@ +/* + * Copyright 2002-2019 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.client; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; +import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * Tests for {@link OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager}. + * + * @author Ankur Pathak + */ +public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests { + private ReactiveClientRegistrationRepository clientRegistrationRepository; + private ReactiveOAuth2AuthorizedClientService authorizedClientService; + private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider; + private Function contextAttributesMapper; + private OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager; + private ClientRegistration clientRegistration; + private Authentication principal; + private OAuth2AuthorizedClient authorizedClient; + private ArgumentCaptor authorizationContextCaptor; + + @SuppressWarnings("unchecked") + @Before + public void setup() { + this.clientRegistrationRepository = mock(ReactiveClientRegistrationRepository.class); + this.authorizedClientService = mock(ReactiveOAuth2AuthorizedClientService.class); + this.authorizedClientProvider = mock(ReactiveOAuth2AuthorizedClientProvider.class); + this.contextAttributesMapper = mock(Function.class); + this.authorizedClientManager = new OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager( + this.clientRegistrationRepository, this.authorizedClientService); + this.authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider); + this.authorizedClientManager.setContextAttributesMapper(this.contextAttributesMapper); + this.clientRegistration = TestClientRegistrations.clientRegistration().build(); + this.principal = new TestingAuthenticationToken("principal", "password"); + this.authorizedClient = new OAuth2AuthorizedClient(this.clientRegistration, this.principal.getName(), + TestOAuth2AccessTokens.scopes("read", "write"), TestOAuth2RefreshTokens.refreshToken()); + this.authorizationContextCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationContext.class); + } + + @Test + public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(null, this.authorizedClientService)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("reactiveClientRegistrationRepository cannot be null"); + } + + @Test + public void constructorWhenOAuth2AuthorizedClientServiceIsNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> new OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(this.clientRegistrationRepository, null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("reactiveAuthorizedClientService cannot be null"); + } + + @Test + public void setAuthorizedClientProviderWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientManager.setAuthorizedClientProvider(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("reactiveAuthorizedClientProvider cannot be null"); + } + + @Test + public void setContextAttributesMapperWhenNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientManager.setContextAttributesMapper(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("contextAttributesMapper cannot be null"); + } + + @Test + public void authorizeWhenRequestIsNullThenThrowIllegalArgumentException() { + assertThatThrownBy(() -> this.authorizedClientManager.authorize(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("authorizeRequest cannot be null"); + } + + @Test + public void authorizeWhenClientRegistrationNotFoundThenThrowIllegalArgumentException() { + String clientRegistrationId = "invalid-registration-id"; + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId) + .principal(this.principal) + .build(); + when(this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)).thenReturn(Mono.empty()); + StepVerifier.create(this.authorizedClientManager.authorize(authorizeRequest)) + .verifyError(IllegalArgumentException.class); + + } + + @SuppressWarnings("unchecked") + @Test + public void authorizeWhenNotAuthorizedAndUnsupportedProviderThenNotAuthorized() { + when(this.clientRegistrationRepository.findByRegistrationId( + eq(this.clientRegistration.getRegistrationId()))).thenReturn(Mono.just(this.clientRegistration)); + when(this.authorizedClientService.loadAuthorizedClient( + any(), any())).thenReturn(Mono.empty()); + + when(authorizedClientProvider.authorize(any())).thenReturn(Mono.empty()); + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(this.clientRegistration.getRegistrationId()) + .principal(this.principal) + .build(); + Mono authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); + + authorizedClient.subscribe(); + + verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); + verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); + + OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); + assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); + assertThat(authorizationContext.getAuthorizedClient()).isNull(); + assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); + + StepVerifier.create(authorizedClient).expectComplete(); + verify(this.authorizedClientService, never()).saveAuthorizedClient( + any(OAuth2AuthorizedClient.class), eq(this.principal)); + } + + @SuppressWarnings("unchecked") + @Test + public void authorizeWhenNotAuthorizedAndSupportedProviderThenAuthorized() { + when(this.clientRegistrationRepository.findByRegistrationId( + eq(this.clientRegistration.getRegistrationId()))).thenReturn(Mono.just(this.clientRegistration)); + + when(this.authorizedClientService.loadAuthorizedClient( + any(), any())).thenReturn(Mono.empty()); + + when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class))).thenReturn(Mono.just(this.authorizedClient)); + + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(this.clientRegistration.getRegistrationId()) + .principal(this.principal) + .build(); + Mono authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); + + authorizedClient.subscribe(); + + verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); + verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); + + OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); + assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); + assertThat(authorizationContext.getAuthorizedClient()).isNull(); + assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); + + StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); + verify(this.authorizedClientService).saveAuthorizedClient( + eq(this.authorizedClient), eq(this.principal)); + } + + @SuppressWarnings("unchecked") + @Test + public void authorizeWhenAuthorizedAndSupportedProviderThenReauthorized() { + when(this.clientRegistrationRepository.findByRegistrationId( + eq(this.clientRegistration.getRegistrationId()))).thenReturn(Mono.just(this.clientRegistration)); + when(this.authorizedClientService.loadAuthorizedClient( + eq(this.clientRegistration.getRegistrationId()), eq(this.principal.getName()))).thenReturn(Mono.just(this.authorizedClient)); + + OAuth2AuthorizedClient reauthorizedClient = new OAuth2AuthorizedClient( + this.clientRegistration, this.principal.getName(), + TestOAuth2AccessTokens.noScopes(), TestOAuth2RefreshTokens.refreshToken()); + + when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class))).thenReturn(Mono.just(reauthorizedClient)); + + OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(this.clientRegistration.getRegistrationId()) + .principal(this.principal) + .build(); + Mono authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); + + authorizedClient.subscribe(); + + verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); + verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); + + OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); + assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); + assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); + assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); + + StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); + verify(this.authorizedClientService).saveAuthorizedClient( + eq(reauthorizedClient), eq(this.principal)); + } + + @SuppressWarnings("unchecked") + @Test + public void reauthorizeWhenUnsupportedProviderThenNotReauthorized() { + when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class))).thenReturn(Mono.empty()); + OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient) + .principal(this.principal) + .build(); + Mono authorizedClient = this.authorizedClientManager.authorize(reauthorizeRequest); + + authorizedClient.subscribe(); + + verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); + verify(this.contextAttributesMapper).apply(eq(reauthorizeRequest)); + + OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); + assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); + assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); + assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); + + StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); + verify(this.authorizedClientService, never()).saveAuthorizedClient( + any(OAuth2AuthorizedClient.class), eq(this.principal)); + } + + @SuppressWarnings("unchecked") + @Test + public void reauthorizeWhenSupportedProviderThenReauthorized() { + OAuth2AuthorizedClient reauthorizedClient = new OAuth2AuthorizedClient( + this.clientRegistration, this.principal.getName(), + TestOAuth2AccessTokens.noScopes(), TestOAuth2RefreshTokens.refreshToken()); + + when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class))).thenReturn(Mono.just(reauthorizedClient)); + + OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient) + .principal(this.principal) + .build(); + Mono authorizedClient = this.authorizedClientManager.authorize(reauthorizeRequest); + + authorizedClient.subscribe(); + + verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); + verify(this.contextAttributesMapper).apply(eq(reauthorizeRequest)); + + OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); + assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); + assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); + assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); + + StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); + verify(this.authorizedClientService).saveAuthorizedClient( + eq(reauthorizedClient), eq(this.principal)); + } + + @SuppressWarnings("unchecked") + @Test + public void reauthorizeWhenRequestAttributeScopeThenMappedToContext() { + OAuth2AuthorizedClient reauthorizedClient = new OAuth2AuthorizedClient( + this.clientRegistration, this.principal.getName(), + TestOAuth2AccessTokens.noScopes(), TestOAuth2RefreshTokens.refreshToken()); + + when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class))).thenReturn(Mono.just(reauthorizedClient)); + + + OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient) + .principal(this.principal) + .attribute(OAuth2ParameterNames.SCOPE, "read write") + .build(); + Mono authorizedClient = this.authorizedClientManager.authorize(reauthorizeRequest); + + authorizedClient.subscribe(); + + verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); + + OAuth2AuthorizationContext authorizationContext = this.authorizationContextCaptor.getValue(); + assertThat(authorizationContext.getClientRegistration()).isEqualTo(this.clientRegistration); + assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); + assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); + assertThat(authorizationContext.getAttributes()).containsKey(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME); + String[] requestScopeAttribute = authorizationContext.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME); + assertThat(requestScopeAttribute).contains("read", "write"); + + StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); + verify(this.authorizedClientService).saveAuthorizedClient( + eq(reauthorizedClient), eq(this.principal)); + } +} From 840d3aa9861d550ff8b90fc6e78f605bb53c8039 Mon Sep 17 00:00:00 2001 From: Phil Clay Date: Thu, 5 Dec 2019 13:14:06 -0800 Subject: [PATCH 009/348] Polish #7589 Rename OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager to AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager. Handle empty mono returned from contextAttributesMapper. Handle empty map returned from contextAttributesMapper. Fix DefaultContextAttributesMapper so that it doesn't access ServerWebExchange. Fix unit tests so that they pass. Use StepVerifier in unit tests, rather than .subscribe(). Fixes gh-7569 --- ...eactiveOAuth2AuthorizedClientManager.java} | 94 +++++++++---------- ...veOAuth2AuthorizedClientManagerTests.java} | 73 ++++++++------ 2 files changed, 87 insertions(+), 80 deletions(-) rename oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/{OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java => AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java} (60%) rename oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/{OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java => AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java} (85%) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java similarity index 60% rename from oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java rename to oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java index 38bf53487c..16400dc922 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.java @@ -15,20 +15,13 @@ */ package org.springframework.security.oauth2.client; -import org.springframework.lang.Nullable; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import java.util.Collections; -import java.util.HashMap; import java.util.Map; -import java.util.Optional; import java.util.function.Function; /** @@ -36,25 +29,31 @@ import java.util.function.Function; * that is capable of operating outside of a {@code ServerHttpRequest} context, * e.g. in a scheduled/background thread and/or in the service-tier. * + *

This is a reactive equivalent of {@link org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager}

+ * * @author Ankur Pathak + * @author Phil Clay * @see ReactiveOAuth2AuthorizedClientManager * @see ReactiveOAuth2AuthorizedClientProvider * @see ReactiveOAuth2AuthorizedClientService - * @since 5.3 + * @since 5.2.2 */ -public final class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager { +public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager + implements ReactiveOAuth2AuthorizedClientManager { + private final ReactiveClientRegistrationRepository clientRegistrationRepository; private final ReactiveOAuth2AuthorizedClientService authorizedClientService; private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty(); private Function>> contextAttributesMapper = new DefaultContextAttributesMapper(); /** - * Constructs an {@code OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters. + * Constructs an {@code AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters. * * @param clientRegistrationRepository the repository of client registrations * @param authorizedClientService the authorized client service */ - public OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(ReactiveClientRegistrationRepository clientRegistrationRepository, + public AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager( + ReactiveClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService authorizedClientService) { Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null"); Assert.notNull(authorizedClientService, "authorizedClientService cannot be null"); @@ -62,35 +61,42 @@ public final class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientMa this.authorizedClientService = authorizedClientService; } - @Nullable @Override public Mono authorize(OAuth2AuthorizeRequest authorizeRequest) { Assert.notNull(authorizeRequest, "authorizeRequest cannot be null"); + + return createAuthorizationContext(authorizeRequest) + .flatMap(this::authorizeAndSave); + } + + private Mono createAuthorizationContext(OAuth2AuthorizeRequest authorizeRequest) { String clientRegistrationId = authorizeRequest.getClientRegistrationId(); - OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient(); Authentication principal = authorizeRequest.getPrincipal(); - // @formatter:off - return Mono.justOrEmpty(authorizedClient) + return Mono.justOrEmpty(authorizeRequest.getAuthorizedClient()) .map(OAuth2AuthorizationContext::withAuthorizedClient) .switchIfEmpty(Mono.defer(() -> this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId) - .flatMap(clientRegistration -> this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName()) - .map(OAuth2AuthorizationContext::withAuthorizedClient) - .switchIfEmpty(Mono.fromSupplier(() -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration))) - ) - .switchIfEmpty(Mono.error(new IllegalArgumentException("Could not find ClientRegistration with id '" + clientRegistrationId + "'"))) - ) - ) + .flatMap(clientRegistration -> this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName()) + .map(OAuth2AuthorizationContext::withAuthorizedClient) + .switchIfEmpty(Mono.fromSupplier(() -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration)))) + .switchIfEmpty(Mono.error(() -> new IllegalArgumentException("Could not find ClientRegistration with id '" + clientRegistrationId + "'"))))) .flatMap(contextBuilder -> this.contextAttributesMapper.apply(authorizeRequest) - .filter(contextAttributes-> !CollectionUtils.isEmpty(contextAttributes)) - .map(contextAttributes -> contextBuilder.principal(principal) - .attributes(attributes -> { - attributes.putAll(contextAttributes); - }).build()) - ).flatMap(authorizationContext -> this.authorizedClientProvider.authorize(authorizationContext) - .doOnNext(_authorizedClient -> authorizedClientService.saveAuthorizedClient(_authorizedClient, principal)) - .switchIfEmpty(Mono.defer(()-> Mono.justOrEmpty(Optional.ofNullable(authorizationContext.getAuthorizedClient())))) - ); - // @formatter:on + .defaultIfEmpty(Collections.emptyMap()) + .map(contextAttributes -> { + OAuth2AuthorizationContext.Builder builder = contextBuilder.principal(principal); + if (!contextAttributes.isEmpty()) { + builder = builder.attributes(attributes -> attributes.putAll(contextAttributes)); + } + return builder.build(); + })); + } + + private Mono authorizeAndSave(OAuth2AuthorizationContext authorizationContext) { + return this.authorizedClientProvider.authorize(authorizationContext) + .flatMap(authorizedClient -> this.authorizedClientService.saveAuthorizedClient( + authorizedClient, + authorizationContext.getPrincipal()) + .thenReturn(authorizedClient)) + .switchIfEmpty(Mono.defer(()-> Mono.justOrEmpty(authorizationContext.getAuthorizedClient()))); } /** @@ -115,33 +121,17 @@ public final class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientMa this.contextAttributesMapper = contextAttributesMapper; } - private static Mono currentServerWebExchange() { - return Mono.subscriberContext() - .filter(c -> c.hasKey(ServerWebExchange.class)) - .map(c -> c.get(ServerWebExchange.class)); - } - /** * The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}. */ public static class DefaultContextAttributesMapper implements Function>> { + private final AuthorizedClientServiceOAuth2AuthorizedClientManager.DefaultContextAttributesMapper mapper = + new AuthorizedClientServiceOAuth2AuthorizedClientManager.DefaultContextAttributesMapper(); + @Override public Mono> apply(OAuth2AuthorizeRequest authorizeRequest) { - ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName()); - return Mono.justOrEmpty(serverWebExchange) - .switchIfEmpty(Mono.defer(() -> currentServerWebExchange())) - .flatMap(exchange -> { - Map contextAttributes = Collections.emptyMap(); - String scope = exchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.SCOPE); - if (StringUtils.hasText(scope)) { - contextAttributes = new HashMap<>(); - contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME, - StringUtils.delimitedListToStringArray(scope, " ")); - } - return Mono.just(contextAttributes); - }) - .defaultIfEmpty(Collections.emptyMap()); + return Mono.fromCallable(() -> mapper.apply(authorizeRequest)); } } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java similarity index 85% rename from oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java rename to oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java index 2be9e4139d..ab9c7ff382 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests.java @@ -28,39 +28,49 @@ import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import reactor.test.publisher.PublisherProbe; +import java.util.Map; import java.util.function.Function; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** - * Tests for {@link OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager}. + * Tests for {@link AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager}. * * @author Ankur Pathak + * @author Phil Clay */ -public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests { +public class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerTests { private ReactiveClientRegistrationRepository clientRegistrationRepository; private ReactiveOAuth2AuthorizedClientService authorizedClientService; private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider; - private Function contextAttributesMapper; - private OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager; + private Function>> contextAttributesMapper; + private AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager authorizedClientManager; private ClientRegistration clientRegistration; private Authentication principal; private OAuth2AuthorizedClient authorizedClient; private ArgumentCaptor authorizationContextCaptor; + private PublisherProbe saveAuthorizedClientProbe; @SuppressWarnings("unchecked") @Before public void setup() { this.clientRegistrationRepository = mock(ReactiveClientRegistrationRepository.class); this.authorizedClientService = mock(ReactiveOAuth2AuthorizedClientService.class); + this.saveAuthorizedClientProbe = PublisherProbe.empty(); + when(this.authorizedClientService.saveAuthorizedClient(any(), any())).thenReturn(this.saveAuthorizedClientProbe.mono()); this.authorizedClientProvider = mock(ReactiveOAuth2AuthorizedClientProvider.class); this.contextAttributesMapper = mock(Function.class); - this.authorizedClientManager = new OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager( + when(this.contextAttributesMapper.apply(any())).thenReturn(Mono.empty()); + this.authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager( this.clientRegistrationRepository, this.authorizedClientService); this.authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider); this.authorizedClientManager.setContextAttributesMapper(this.contextAttributesMapper); @@ -73,23 +83,23 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT @Test public void constructorWhenClientRegistrationRepositoryIsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(null, this.authorizedClientService)) + assertThatThrownBy(() -> new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(null, this.authorizedClientService)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("reactiveClientRegistrationRepository cannot be null"); + .hasMessage("clientRegistrationRepository cannot be null"); } @Test public void constructorWhenOAuth2AuthorizedClientServiceIsNullThenThrowIllegalArgumentException() { - assertThatThrownBy(() -> new OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(this.clientRegistrationRepository, null)) + assertThatThrownBy(() -> new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(this.clientRegistrationRepository, null)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("reactiveAuthorizedClientService cannot be null"); + .hasMessage("authorizedClientService cannot be null"); } @Test public void setAuthorizedClientProviderWhenNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> this.authorizedClientManager.setAuthorizedClientProvider(null)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("reactiveAuthorizedClientProvider cannot be null"); + .hasMessage("authorizedClientProvider cannot be null"); } @Test @@ -132,7 +142,7 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT .build(); Mono authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - authorizedClient.subscribe(); + StepVerifier.create(authorizedClient).verifyComplete(); verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); @@ -142,7 +152,6 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT assertThat(authorizationContext.getAuthorizedClient()).isNull(); assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); - StepVerifier.create(authorizedClient).expectComplete(); verify(this.authorizedClientService, never()).saveAuthorizedClient( any(OAuth2AuthorizedClient.class), eq(this.principal)); } @@ -163,7 +172,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT .build(); Mono authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - authorizedClient.subscribe(); + StepVerifier.create(authorizedClient) + .expectNext(this.authorizedClient) + .verifyComplete(); verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); @@ -173,9 +184,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT assertThat(authorizationContext.getAuthorizedClient()).isNull(); assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); - StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); verify(this.authorizedClientService).saveAuthorizedClient( eq(this.authorizedClient), eq(this.principal)); + this.saveAuthorizedClientProbe.assertWasSubscribed(); } @SuppressWarnings("unchecked") @@ -197,8 +208,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT .build(); Mono authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - authorizedClient.subscribe(); - + StepVerifier.create(authorizedClient) + .expectNext(reauthorizedClient) + .verifyComplete(); verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); verify(this.contextAttributesMapper).apply(eq(authorizeRequest)); @@ -207,9 +219,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); - StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); verify(this.authorizedClientService).saveAuthorizedClient( eq(reauthorizedClient), eq(this.principal)); + this.saveAuthorizedClientProbe.assertWasSubscribed(); } @SuppressWarnings("unchecked") @@ -221,8 +233,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT .build(); Mono authorizedClient = this.authorizedClientManager.authorize(reauthorizeRequest); - authorizedClient.subscribe(); - + StepVerifier.create(authorizedClient) + .expectNext(this.authorizedClient) + .verifyComplete(); verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); verify(this.contextAttributesMapper).apply(eq(reauthorizeRequest)); @@ -231,7 +244,6 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); - StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); verify(this.authorizedClientService, never()).saveAuthorizedClient( any(OAuth2AuthorizedClient.class), eq(this.principal)); } @@ -250,7 +262,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT .build(); Mono authorizedClient = this.authorizedClientManager.authorize(reauthorizeRequest); - authorizedClient.subscribe(); + StepVerifier.create(authorizedClient) + .expectNext(reauthorizedClient) + .verifyComplete(); verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); verify(this.contextAttributesMapper).apply(eq(reauthorizeRequest)); @@ -260,9 +274,9 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT assertThat(authorizationContext.getAuthorizedClient()).isSameAs(this.authorizedClient); assertThat(authorizationContext.getPrincipal()).isEqualTo(this.principal); - StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); verify(this.authorizedClientService).saveAuthorizedClient( eq(reauthorizedClient), eq(this.principal)); + this.saveAuthorizedClientProbe.assertWasSubscribed(); } @SuppressWarnings("unchecked") @@ -274,14 +288,20 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT when(this.authorizedClientProvider.authorize(any(OAuth2AuthorizationContext.class))).thenReturn(Mono.just(reauthorizedClient)); - OAuth2AuthorizeRequest reauthorizeRequest = OAuth2AuthorizeRequest.withAuthorizedClient(this.authorizedClient) .principal(this.principal) .attribute(OAuth2ParameterNames.SCOPE, "read write") .build(); + + this.authorizedClientManager.setContextAttributesMapper(new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.DefaultContextAttributesMapper()); Mono authorizedClient = this.authorizedClientManager.authorize(reauthorizeRequest); - authorizedClient.subscribe(); + StepVerifier.create(authorizedClient) + .expectNext(reauthorizedClient) + .verifyComplete(); + verify(this.authorizedClientService).saveAuthorizedClient( + eq(reauthorizedClient), eq(this.principal)); + this.saveAuthorizedClientProbe.assertWasSubscribed(); verify(this.authorizedClientProvider).authorize(this.authorizationContextCaptor.capture()); @@ -293,8 +313,5 @@ public class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManagerT String[] requestScopeAttribute = authorizationContext.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME); assertThat(requestScopeAttribute).contains("read", "write"); - StepVerifier.create(authorizedClient).expectNextCount(1).assertNext(x -> assertThat(x).isSameAs(this.authorizedClient)); - verify(this.authorizedClientService).saveAuthorizedClient( - eq(reauthorizedClient), eq(this.principal)); } } From 6db7b457b7cab698264732c7bfa19e4699a79f1c Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 11 Dec 2019 15:39:27 -0600 Subject: [PATCH 010/348] DelegatingServerLogoutHandler Executes Sequentially Fixes gh-7723 --- .../logout/DelegatingServerLogoutHandler.java | 11 +++----- .../DelegatingServerLogoutHandlerTests.java | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandler.java index 3a6a6fc400..9b6567227d 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandler.java @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.security.core.Authentication; @@ -48,12 +49,8 @@ public class DelegatingServerLogoutHandler implements ServerLogoutHandler { @Override public Mono logout(WebFilterExchange exchange, Authentication authentication) { - List> results = new ArrayList<>(); - for (ServerLogoutHandler delegate : delegates) { - if (delegate != null) { - results.add(delegate.logout(exchange, authentication)); - } - } - return Mono.when(results); + return Flux.fromIterable(this.delegates) + .concatMap(delegate -> delegate.logout(exchange, authentication)) + .then(); } } diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandlerTests.java index 76c790c42f..01a304c0fe 100644 --- a/web/src/test/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandlerTests.java +++ b/web/src/test/java/org/springframework/security/web/server/authentication/logout/DelegatingServerLogoutHandlerTests.java @@ -16,6 +16,7 @@ package org.springframework.security.web.server.authentication.logout; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.*; @@ -28,9 +29,14 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; +import reactor.core.publisher.Mono; import reactor.test.publisher.PublisherProbe; +import java.time.Duration; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; /** * @author Eric Deandrea @@ -98,4 +104,26 @@ public class DelegatingServerLogoutHandlerTests { this.delegate1Result.assertWasSubscribed(); this.delegate2Result.assertWasSubscribed(); } + + @Test + public void logoutSequential() throws Exception { + AtomicBoolean slowDone = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + ServerLogoutHandler slow = (exchange, authentication) -> + Mono.delay(Duration.ofMillis(100)) + .doOnSuccess(__ -> slowDone.set(true)) + .then(); + ServerLogoutHandler second = (exchange, authentication) -> + Mono.fromRunnable(() -> { + latch.countDown(); + assertThat(slowDone.get()) + .describedAs("ServerLogoutHandler should be executed sequentially") + .isTrue(); + }); + DelegatingServerLogoutHandler handler = new DelegatingServerLogoutHandler(slow, second); + + handler.logout(this.exchange, this.authentication).block(); + + assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); + } } From bd6ff1f319cb810dc362b851040b426d79c0e115 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 12 Dec 2019 08:32:25 -0600 Subject: [PATCH 011/348] DelegatingServerAuthenticationSuccessHandler Executes Sequentially Fixes gh-7728 --- ...ingServerAuthenticationSuccessHandler.java | 10 ++--- ...rverAuthenticationSuccessHandlerTests.java | 38 ++++++++++++++++--- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java index 6506526e7c..019442b5af 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandler.java @@ -19,11 +19,11 @@ package org.springframework.security.web.server.authentication; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; import org.springframework.util.Assert; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.List; -import java.util.ArrayList; /** * Delegates to a collection of {@link ServerAuthenticationSuccessHandler} implementations. @@ -42,10 +42,8 @@ public class DelegatingServerAuthenticationSuccessHandler implements ServerAuthe @Override public Mono onAuthenticationSuccess(WebFilterExchange exchange, Authentication authentication) { - List> results = new ArrayList<>(); - for (ServerAuthenticationSuccessHandler delegate : delegates) { - results.add(delegate.onAuthenticationSuccess(exchange, authentication)); - } - return Mono.when(results); + return Flux.fromIterable(this.delegates) + .concatMap(delegate -> delegate.onAuthenticationSuccess(exchange, authentication)) + .then(); } } diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java index 2e0da4a174..cd4432416e 100644 --- a/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java +++ b/web/src/test/java/org/springframework/security/web/server/authentication/DelegatingServerAuthenticationSuccessHandlerTests.java @@ -16,10 +16,6 @@ package org.springframework.security.web.server.authentication; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,9 +23,19 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.security.core.Authentication; import org.springframework.security.web.server.WebFilterExchange; - +import reactor.core.publisher.Mono; import reactor.test.publisher.PublisherProbe; +import java.time.Duration; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + /** * @author Rob Winch * @since 5.1 @@ -88,4 +94,26 @@ public class DelegatingServerAuthenticationSuccessHandlerTests { this.delegate1Result.assertWasSubscribed(); this.delegate2Result.assertWasSubscribed(); } + + @Test + public void onAuthenticationSuccessSequential() throws Exception { + AtomicBoolean slowDone = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + ServerAuthenticationSuccessHandler slow = (exchange, authentication) -> + Mono.delay(Duration.ofMillis(100)) + .doOnSuccess(__ -> slowDone.set(true)) + .then(); + ServerAuthenticationSuccessHandler second = (exchange, authentication) -> + Mono.fromRunnable(() -> { + latch.countDown(); + assertThat(slowDone.get()) + .describedAs("ServerAuthenticationSuccessHandler should be executed sequentially") + .isTrue(); + }); + DelegatingServerAuthenticationSuccessHandler handler = new DelegatingServerAuthenticationSuccessHandler(slow, second); + + handler.onAuthenticationSuccess(this.exchange, this.authentication).block(); + + assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); + } } From 29eb8b91776ebdf35f0c29d5e0d64b4a5ec10194 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 12 Dec 2019 11:23:39 -0600 Subject: [PATCH 012/348] CompositeServerHttpHeadersWriter Executes Sequentially Fixes gh-7731 --- .../CompositeServerHttpHeadersWriter.java | 17 ++++---- ...CompositeServerHttpHeadersWriterTests.java | 42 ++++++++++++++----- 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriter.java index 0674d2225d..2ddb9b1e18 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriter.java @@ -15,13 +15,12 @@ */ package org.springframework.security.web.server.header; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + import java.util.Arrays; import java.util.List; -import java.util.ArrayList; - -import org.springframework.web.server.ServerWebExchange; - -import reactor.core.publisher.Mono; /** * Combines multiple {@link ServerHttpHeadersWriter} instances into a single instance. @@ -42,10 +41,8 @@ public class CompositeServerHttpHeadersWriter implements ServerHttpHeadersWriter @Override public Mono writeHttpHeaders(ServerWebExchange exchange) { - List> results = new ArrayList<>(); - for (ServerHttpHeadersWriter writer : writers) { - results.add(writer.writeHttpHeaders(exchange)); - } - return Mono.when(results); + return Flux.fromIterable(this.writers) + .concatMap(w -> w.writeHttpHeaders(exchange)) + .then(); } } diff --git a/web/src/test/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriterTests.java index 1b4c169ff4..9ab68a5375 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/CompositeServerHttpHeadersWriterTests.java @@ -15,11 +15,6 @@ */ package org.springframework.security.web.server.header; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Arrays; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,10 +23,19 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; - import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.time.Duration; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + /** * * @author Rob Winch @@ -55,7 +59,6 @@ public class CompositeServerHttpHeadersWriterTests { @Test public void writeHttpHeadersWhenErrorNoErrorThenError() { when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException())); - when(writer2.writeHttpHeaders(exchange)).thenReturn(Mono.empty()); Mono result = writer.writeHttpHeaders(exchange); @@ -64,13 +67,11 @@ public class CompositeServerHttpHeadersWriterTests { .verify(); verify(writer1).writeHttpHeaders(exchange); - verify(writer2).writeHttpHeaders(exchange); } @Test public void writeHttpHeadersWhenErrorErrorThenError() { when(writer1.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException())); - when(writer2.writeHttpHeaders(exchange)).thenReturn(Mono.error(new RuntimeException())); Mono result = writer.writeHttpHeaders(exchange); @@ -79,7 +80,6 @@ public class CompositeServerHttpHeadersWriterTests { .verify(); verify(writer1).writeHttpHeaders(exchange); - verify(writer2).writeHttpHeaders(exchange); } @Test @@ -96,4 +96,26 @@ public class CompositeServerHttpHeadersWriterTests { verify(writer1).writeHttpHeaders(exchange); verify(writer2).writeHttpHeaders(exchange); } + + @Test + public void writeHttpHeadersSequential() throws Exception { + AtomicBoolean slowDone = new AtomicBoolean(); + CountDownLatch latch = new CountDownLatch(1); + ServerHttpHeadersWriter slow = exchange -> + Mono.delay(Duration.ofMillis(100)) + .doOnSuccess(__ -> slowDone.set(true)) + .then(); + ServerHttpHeadersWriter second = exchange -> + Mono.fromRunnable(() -> { + latch.countDown(); + assertThat(slowDone.get()) + .describedAs("ServerLogoutHandler should be executed sequentially") + .isTrue(); + }); + CompositeServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter(slow, second); + + writer.writeHttpHeaders(this.exchange).block(); + + assertThat(latch.await(3, TimeUnit.SECONDS)).isTrue(); + } } From 0782228914b46919a0bfc126e0045eed92565957 Mon Sep 17 00:00:00 2001 From: Clement Stoquart Date: Thu, 28 Nov 2019 15:45:37 +0100 Subject: [PATCH 013/348] fix: make Saml2Authentication serializable --- .../OpenSamlAuthenticationProvider.java | 2 +- .../Saml2AuthenticatedPrincipal.java | 28 +++++++++++++ .../SimpleSaml2AuthenticatedPrincipal.java | 39 +++++++++++++++++++ .../OpenSamlAuthenticationProviderTests.java | 26 +++++++++++++ ...impleSaml2AuthenticatedPrincipalTests.java | 30 ++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java create mode 100644 saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipalTests.java diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java index a6e5034d23..900f3cd820 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java @@ -178,7 +178,7 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi Assertion assertion = validateSaml2Response(token, token.getRecipientUri(), samlResponse); String username = getUsername(token, assertion); return new Saml2Authentication( - () -> username, token.getSaml2Response(), + new SimpleSaml2AuthenticatedPrincipal(username), token.getSaml2Response(), this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion)) ); } catch (Saml2AuthenticationException e) { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java new file mode 100644 index 0000000000..5767b55e41 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java @@ -0,0 +1,28 @@ +/* + * Copyright 2002-2019 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.saml2.provider.service.authentication; + +import org.springframework.security.core.AuthenticatedPrincipal; + +/** + * Saml2 representation of an {@link AuthenticatedPrincipal}. + * + * @author Clement Stoquart + * @since 5.3 + */ +public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { +} diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java new file mode 100644 index 0000000000..8592571037 --- /dev/null +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2019 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.saml2.provider.service.authentication; + +import java.io.Serializable; + +/** + * Default implementation of a {@link Saml2AuthenticatedPrincipal}. + * + * @author Clement Stoquart + * @since 5.3 + */ +class SimpleSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable { + + private final String name; + + SimpleSaml2AuthenticatedPrincipal(String name) { + this.name = name; + } + + @Override + public String getName() { + return this.name; + } +} diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java index 3424270b87..387302323e 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java @@ -16,6 +16,10 @@ package org.springframework.security.saml2.provider.service.authentication; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; + import org.springframework.security.core.Authentication; import org.hamcrest.BaseMatcher; @@ -346,6 +350,28 @@ public class OpenSamlAuthenticationProviderTests { provider.authenticate(token); } + @Test + public void writeObjectWhenTypeIsSaml2AuthenticationThenNoException() throws IOException { + Response response = response(recipientUri, idpEntityId); + Assertion assertion = defaultAssertion(); + signXmlObject( + assertion, + assertingPartyCredentials(), + recipientEntityId + ); + EncryptedAssertion encryptedAssertion = encryptAssertion(assertion, assertingPartyCredentials()); + response.getEncryptedAssertions().add(encryptedAssertion); + token = responseXml(response, idpEntityId); + + Saml2Authentication authentication = (Saml2Authentication) provider.authenticate(token); + + // the following code will throw an exception if authentication isn't serializable + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(1024); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream); + objectOutputStream.writeObject(authentication); + objectOutputStream.flush(); + } + private Assertion defaultAssertion() { return assertion( username, diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipalTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipalTests.java new file mode 100644 index 0000000000..5948ab7ca9 --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipalTests.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2019 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.saml2.provider.service.authentication; + +import org.junit.Assert; +import org.junit.Test; + +public class SimpleSaml2AuthenticatedPrincipalTests { + + @Test + public void createSimpleSaml2AuthenticatedPrincipal() { + SimpleSaml2AuthenticatedPrincipal principal = new SimpleSaml2AuthenticatedPrincipal("user"); + + Assert.assertEquals("user", principal.getName()); + } +} From 59ca2ddf65a73c3ecb0425e69b8ffc5f273c1b5c Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Dec 2019 20:22:58 +0100 Subject: [PATCH 014/348] Polish SAML2 principal classes Update @since Issue: gh-7681 --- .../service/authentication/Saml2AuthenticatedPrincipal.java | 2 +- .../authentication/SimpleSaml2AuthenticatedPrincipal.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java index 5767b55e41..97bc90d65f 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/Saml2AuthenticatedPrincipal.java @@ -22,7 +22,7 @@ import org.springframework.security.core.AuthenticatedPrincipal; * Saml2 representation of an {@link AuthenticatedPrincipal}. * * @author Clement Stoquart - * @since 5.3 + * @since 5.2.2 */ public interface Saml2AuthenticatedPrincipal extends AuthenticatedPrincipal { } diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java index 8592571037..3eb752c46a 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/SimpleSaml2AuthenticatedPrincipal.java @@ -22,7 +22,7 @@ import java.io.Serializable; * Default implementation of a {@link Saml2AuthenticatedPrincipal}. * * @author Clement Stoquart - * @since 5.3 + * @since 5.2.2 */ class SimpleSaml2AuthenticatedPrincipal implements Saml2AuthenticatedPrincipal, Serializable { From b00999deed40d22b78472a28b41a17808d98afdf Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 11 Dec 2019 08:56:40 -0600 Subject: [PATCH 015/348] Docs ServerRSocketFactoryCustomizer->ServerRSocketFactoryProcessor The documentation incorrectly used ServerRSocketFactoryCustomizer which was renamed to ServerRSocketFactoryProcessor. The docs now use the correct class name Fixes gh-7737 --- docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc index aae35bfbed..0bbf66f735 100644 --- a/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/reactive/rsocket.adoc @@ -38,12 +38,12 @@ This configuration enables <> For Spring Security to work we need to apply `SecuritySocketAcceptorInterceptor` to the `ServerRSocketFactory`. This is what connects our `PayloadSocketAcceptorInterceptor` we created with the RSocket infrastructure. -In a Spring Boot application this can be done using the following code. +In a Spring Boot application this is done automatically using `RSocketSecurityAutoConfiguration` with the following code. [source,java] ---- @Bean -ServerRSocketFactoryCustomizer springSecurityRSocketSecurity( +ServerRSocketFactoryProcessor springSecurityRSocketSecurity( SecuritySocketAcceptorInterceptor interceptor) { return builder -> builder.addSocketAcceptorPlugin(interceptor); } From 0d24e2b8cfc872327656907e4fc889351bcaacc7 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 28 Nov 2019 14:40:25 +0100 Subject: [PATCH 016/348] Fix WebFlux logout disabling Fixes: gh-7682 --- .../config/web/server/ServerHttpSecurity.java | 4 ++- .../configurers/LogoutConfigurerTests.java | 21 +++++++++++ .../config/web/server/LogoutSpecTests.java | 36 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index cb719cd448..a96da865cc 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -2775,7 +2775,9 @@ public class ServerHttpSecurity { protected void configure(ServerHttpSecurity http) { if (this.csrfTokenRepository != null) { this.filter.setCsrfTokenRepository(this.csrfTokenRepository); - http.logout().addLogoutHandler(new CsrfServerLogoutHandler(this.csrfTokenRepository)); + if (ServerHttpSecurity.this.logout != null) { + ServerHttpSecurity.this.logout.addLogoutHandler(new CsrfServerLogoutHandler(this.csrfTokenRepository)); + } } http.addFilterAt(this.filter, SecurityWebFiltersOrder.CSRF); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java index 86bf87a6be..bb24708390 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java @@ -458,4 +458,25 @@ public class LogoutConfigurerTests { @EnableWebSecurity static class BasicSecurityConfig extends WebSecurityConfigurerAdapter { } + + @Test + public void logoutWhenDisabledThenLogoutUrlNotFound() throws Exception { + this.spring.register(LogoutDisabledConfig.class).autowire(); + + this.mvc.perform(post("/logout") + .with(csrf())) + .andExpect(status().isNotFound()); + } + + @EnableWebSecurity + static class LogoutDisabledConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .logout() + .disable(); + // @formatter:on + } + } } diff --git a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java index 4840ba5499..e417a3cda5 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java @@ -164,4 +164,40 @@ public class LogoutSpecTests { .assertAt() .assertLogout(); } + + @Test + public void logoutWhenDisabledThenPostToLogoutDoesNothing() { + SecurityWebFilterChain securityWebFilter = this.http + .authorizeExchange() + .anyExchange().authenticated() + .and() + .formLogin().and() + .logout().disable() + .build(); + + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .build(); + + WebDriver driver = WebTestClientHtmlUnitDriverBuilder + .webTestClientSetup(webTestClient) + .build(); + + FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class) + .assertAt(); + + FormLoginTests.HomePage homePage = loginPage.loginForm() + .username("user") + .password("password") + .submit(FormLoginTests.HomePage.class); + + homePage.assertAt(); + + FormLoginTests.DefaultLogoutPage.to(driver) + .assertAt() + .logout(); + + homePage + .assertAt(); + } } From b754a3d6353112fd44c41ffed9b89649caaadae4 Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Thu, 12 Dec 2019 11:54:46 -0800 Subject: [PATCH 017/348] Use the custom ServerRequestCache that the user configures on for the default authentication entry point and authentication success handler Fixes gh-7721 https://github.com/spring-projects/spring-security/issues/7721 Set RequestCache on the Oauth2LoginSpec default authentication success handler import static ReflectionTestUtils.getField Feedback incorporated per https://github.com/spring-projects/spring-security/pull/7734#pullrequestreview-332150359 --- .../config/web/server/ServerHttpSecurity.java | 23 ++++++++-- .../web/server/ServerHttpSecurityTests.java | 44 ++++++++++++++++--- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index a96da865cc..e395752cdd 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -76,9 +76,11 @@ import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserSer import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationCodeGrantWebFilter; import org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter; +import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationCodeAuthenticationTokenConverter; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.oidc.user.OidcUser; @@ -984,7 +986,7 @@ public class ServerHttpSecurity { private ServerWebExchangeMatcher authenticationMatcher; - private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler(); + private ServerAuthenticationSuccessHandler authenticationSuccessHandler; private ServerAuthenticationFailureHandler authenticationFailureHandler; @@ -1175,7 +1177,7 @@ public class ServerHttpSecurity { authenticationFilter.setRequiresAuthenticationMatcher(getAuthenticationMatcher()); authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository)); - authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler); + authenticationFilter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler(http)); authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler()); authenticationFilter.setSecurityContextRepository(this.securityContextRepository); @@ -1183,16 +1185,29 @@ public class ServerHttpSecurity { MediaType.TEXT_HTML); htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); Map urlToText = http.oauth2Login.getLinks(); + String authenticationEntryPointRedirectPath; if (urlToText.size() == 1) { - http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, new RedirectServerAuthenticationEntryPoint(urlToText.keySet().iterator().next()))); + authenticationEntryPointRedirectPath = urlToText.keySet().iterator().next(); } else { - http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, new RedirectServerAuthenticationEntryPoint("/login"))); + authenticationEntryPointRedirectPath = "/login"; } + RedirectServerAuthenticationEntryPoint entryPoint = new RedirectServerAuthenticationEntryPoint(authenticationEntryPointRedirectPath); + entryPoint.setRequestCache(http.requestCache.requestCache); + http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, entryPoint)); http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC); http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION); } + private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) { + if (this.authenticationSuccessHandler == null) { + RedirectServerAuthenticationSuccessHandler handler = new RedirectServerAuthenticationSuccessHandler(); + handler.setRequestCache(http.requestCache.requestCache); + this.authenticationSuccessHandler = handler; + } + return this.authenticationSuccessHandler; + } + private ServerAuthenticationFailureHandler getAuthenticationFailureHandler() { if (this.authenticationFailureHandler == null) { this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler("/login?error"); diff --git a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java index c84e79e64a..052b6629a4 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java @@ -20,10 +20,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.springframework.security.config.Customizer.withDefaults; +import static org.springframework.test.util.ReflectionTestUtils.getField; import java.util.Arrays; import java.util.List; @@ -35,16 +37,20 @@ import org.apache.http.HttpHeaders; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; +import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter; +import org.springframework.security.web.server.savedrequest.ServerRequestCache; +import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache; import reactor.core.publisher.Mono; import reactor.test.publisher.TestPublisher; @@ -64,7 +70,6 @@ import org.springframework.security.web.server.context.WebSessionServerSecurityC import org.springframework.security.web.server.csrf.CsrfServerLogoutHandler; import org.springframework.security.web.server.csrf.CsrfWebFilter; import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository; -import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.reactive.server.EntityExchangeResult; import org.springframework.test.web.reactive.server.FluxExchangeResult; import org.springframework.test.web.reactive.server.WebTestClient; @@ -200,7 +205,7 @@ public class ServerHttpSecurityTests { .isNotPresent(); Optional logoutHandler = getWebFilter(securityWebFilterChain, LogoutWebFilter.class) - .map(logoutWebFilter -> (ServerLogoutHandler) ReflectionTestUtils.getField(logoutWebFilter, LogoutWebFilter.class, "logoutHandler")); + .map(logoutWebFilter -> (ServerLogoutHandler) getField(logoutWebFilter, LogoutWebFilter.class, "logoutHandler")); assertThat(logoutHandler) .get() @@ -213,17 +218,17 @@ public class ServerHttpSecurityTests { assertThat(getWebFilter(securityWebFilterChain, CsrfWebFilter.class)) .get() - .extracting(csrfWebFilter -> ReflectionTestUtils.getField(csrfWebFilter, "csrfTokenRepository")) + .extracting(csrfWebFilter -> getField(csrfWebFilter, "csrfTokenRepository")) .isEqualTo(this.csrfTokenRepository); Optional logoutHandler = getWebFilter(securityWebFilterChain, LogoutWebFilter.class) - .map(logoutWebFilter -> (ServerLogoutHandler) ReflectionTestUtils.getField(logoutWebFilter, LogoutWebFilter.class, "logoutHandler")); + .map(logoutWebFilter -> (ServerLogoutHandler) getField(logoutWebFilter, LogoutWebFilter.class, "logoutHandler")); assertThat(logoutHandler) .get() .isExactlyInstanceOf(DelegatingServerLogoutHandler.class) .extracting(delegatingLogoutHandler -> - ((List) ReflectionTestUtils.getField(delegatingLogoutHandler, DelegatingServerLogoutHandler.class, "delegates")).stream() + ((List) getField(delegatingLogoutHandler, DelegatingServerLogoutHandler.class, "delegates")).stream() .map(ServerLogoutHandler::getClass) .collect(Collectors.toList())) .isEqualTo(Arrays.asList(SecurityContextServerLogoutHandler.class, CsrfServerLogoutHandler.class)); @@ -479,6 +484,33 @@ public class ServerHttpSecurityTests { verify(customServerCsrfTokenRepository).loadToken(any()); } + @Test + public void shouldConfigureRequestCacheForOAuth2LoginAuthenticationEntryPointAndSuccessHandler() { + ServerRequestCache requestCache = spy(new WebSessionServerRequestCache()); + ReactiveClientRegistrationRepository clientRegistrationRepository = mock(ReactiveClientRegistrationRepository.class); + + SecurityWebFilterChain securityFilterChain = this.http + .oauth2Login() + .clientRegistrationRepository(clientRegistrationRepository) + .and() + .authorizeExchange().anyExchange().authenticated() + .and() + .requestCache(c -> c.requestCache(requestCache)) + .build(); + + WebTestClient client = WebTestClientBuilder.bindToWebFilters(securityFilterChain).build(); + client.get().uri("/test").exchange(); + ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); + verify(requestCache).saveRequest(captor.capture()); + assertThat(captor.getValue().getRequest().getURI().toString()).isEqualTo("/test"); + + + OAuth2LoginAuthenticationWebFilter authenticationWebFilter = + getWebFilter(securityFilterChain, OAuth2LoginAuthenticationWebFilter.class).get(); + Object handler = getField(authenticationWebFilter, "authenticationSuccessHandler"); + assertThat(getField(handler, "requestCache")).isSameAs(requestCache); + } + @Test public void shouldConfigureAuthorizationRequestRepositoryForOAuth2Login() { ServerAuthorizationRequestRepository authorizationRequestRepository = mock(ServerAuthorizationRequestRepository.class); @@ -503,7 +535,7 @@ public class ServerHttpSecurityTests { private boolean isX509Filter(WebFilter filter) { try { - Object converter = ReflectionTestUtils.getField(filter, "authenticationConverter"); + Object converter = getField(filter, "authenticationConverter"); return converter.getClass().isAssignableFrom(ServerX509AuthenticationConverter.class); } catch (IllegalArgumentException e) { // field doesn't exist From 29182abb34db9df95b528ddb0ae2dcb1237a2a7b Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 9 Jan 2020 14:24:35 -0600 Subject: [PATCH 018/348] Fix HttpHeaderWriterWebFilterTests Ensure setComplete() is subscribed to --- .../web/server/header/HttpHeaderWriterWebFilterTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/test/java/org/springframework/security/web/server/header/HttpHeaderWriterWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/header/HttpHeaderWriterWebFilterTests.java index f83511cee1..3d021b2300 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/HttpHeaderWriterWebFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/HttpHeaderWriterWebFilterTests.java @@ -67,7 +67,7 @@ public class HttpHeaderWriterWebFilterTests { verify(writer, never()).writeHttpHeaders(any()); - result.getExchange().getResponse().setComplete(); + result.getExchange().getResponse().setComplete().block(); verify(writer).writeHttpHeaders(any()); } From cc956a66dfdac78668bafc37755d760120efec5a Mon Sep 17 00:00:00 2001 From: Johannes Edmeier Date: Sat, 14 Dec 2019 10:58:48 +0100 Subject: [PATCH 019/348] Don't cache requests with `Accept: text/event-stream` by default. The eventstream requests is typically not directly invoked by the browser. And even more unfortunately the Browser-Api doesn't allow the set additional headers as `XMLHttpRequest`.. --- .../web/configurers/RequestCacheConfigurer.java | 1 + .../configurers/RequestCacheConfigurerTests.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java index 0e5322634a..3ac5d73e89 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java @@ -162,6 +162,7 @@ public final class RequestCacheConfigurer> exte matchers.add(notMatchingMediaType(http, MediaType.APPLICATION_JSON)); matchers.add(notXRequestedWith); matchers.add(notMatchingMediaType(http, MediaType.MULTIPART_FORM_DATA)); + matchers.add(notMatchingMediaType(http, MediaType.TEXT_EVENT_STREAM)); return new AndRequestMatcher(matchers); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java index 567fe98107..c2ec1211f5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -183,6 +183,21 @@ public class RequestCacheConfigurerTests { // This is desirable since XHR requests are typically not invoked directly from the browser and we don't want the browser to replay them } + @Test + public void getWhenBookmarkedRequestIsTextEventStreamThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + + MockHttpSession session = (MockHttpSession) + this.mvc.perform(get("/messages") + .header(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM)) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn().getRequest().getSession(); + + this.mvc.perform(formLogin(session)) + .andExpect(redirectedUrl("/")); // ignores text/event-stream + + // This is desirable since event-stream requests are typically not invoked directly from the browser and we don't want the browser to replay them + } @Test public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { From f4d4c08329605ce134625ea61e59a6a5faf554de Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 17 Jan 2020 09:24:22 +0100 Subject: [PATCH 020/348] Fix LDIF file example in LDAP docs Fixes: gh-7832 --- .../docs/asciidoc/_includes/servlet/appendix/namespace.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc index 88fa302c0e..e1e01974a6 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/namespace.adoc @@ -2372,8 +2372,8 @@ A bean identifier, used for referring to the bean elsewhere in the context. [[nsa-ldap-server-ldif]] * **ldif** Explicitly specifies an ldif file resource to load into an embedded LDAP server. -The ldiff is should be a Spring resource pattern (i.e. classpath:init.ldiff). -The default is classpath*:*.ldiff +The ldif should be a Spring resource pattern (i.e. classpath:init.ldif). +The default is classpath*:*.ldif [[nsa-ldap-server-manager-dn]] From 630eb1070476234f7319d2c7abe98c45d03ba1fc Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Jan 2020 14:44:02 +0100 Subject: [PATCH 021/348] Load LDIF file from classpath in unboundId mode Fixes: gh-7833 --- .../server/UnboundIdContainerLdifTests.java | 144 ++++++++++++++++++ .../resources/test-server-malformed.txt | 9 ++ .../ldap/server/UnboundIdContainer.java | 6 +- 3 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 ldap/src/integration-test/java/org/springframework/security/ldap/server/UnboundIdContainerLdifTests.java create mode 100644 ldap/src/integration-test/resources/test-server-malformed.txt diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/server/UnboundIdContainerLdifTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/server/UnboundIdContainerLdifTests.java new file mode 100644 index 0000000000..a407a244e8 --- /dev/null +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/server/UnboundIdContainerLdifTests.java @@ -0,0 +1,144 @@ +/* + * 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.ldap.server; + +import org.junit.After; +import org.junit.Test; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.ldap.core.ContextSource; +import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.SpringSecurityLdapTemplate; + +import javax.annotation.PreDestroy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; + +/** + * Tests for {@link UnboundIdContainer}, specifically relating to LDIF file detection. + * + * @author Eleftheria Stein + */ +public class UnboundIdContainerLdifTests { + + AnnotationConfigApplicationContext appCtx; + + @After + public void closeAppContext() { + if (appCtx != null) { + appCtx.close(); + appCtx = null; + } + } + + @Test + public void unboundIdContainerWhenCustomLdifNameThenLdifLoaded() { + appCtx = new AnnotationConfigApplicationContext(CustomLdifConfig.class); + + DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx + .getBean(ContextSource.class); + + SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource); + assertThat(template.compare("uid=bob,ou=people", "uid", "bob")).isTrue(); + } + + @Configuration + static class CustomLdifConfig { + private UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org", + "classpath:test-server.ldif"); + + @Bean + UnboundIdContainer ldapContainer() { + this.container.setPort(0); + return this.container; + } + + @Bean + ContextSource contextSource(UnboundIdContainer container) { + return new DefaultSpringSecurityContextSource("ldap://127.0.0.1:" + + container.getPort() + "/dc=springframework,dc=org"); + } + + @PreDestroy + void shutdown() { + this.container.stop(); + } + } + + @Test + public void unboundIdContainerWhenWildcardLdifNameThenLdifLoaded() { + appCtx = new AnnotationConfigApplicationContext(WildcardLdifConfig.class); + + DefaultSpringSecurityContextSource contextSource = (DefaultSpringSecurityContextSource) appCtx + .getBean(ContextSource.class); + + SpringSecurityLdapTemplate template = new SpringSecurityLdapTemplate(contextSource); + assertThat(template.compare("uid=bob,ou=people", "uid", "bob")).isTrue(); + } + + @Configuration + static class WildcardLdifConfig { + private UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org", + "classpath*:test-server.ldif"); + + @Bean + UnboundIdContainer ldapContainer() { + this.container.setPort(0); + return this.container; + } + + @Bean + ContextSource contextSource(UnboundIdContainer container) { + return new DefaultSpringSecurityContextSource("ldap://127.0.0.1:" + + container.getPort() + "/dc=springframework,dc=org"); + } + + @PreDestroy + void shutdown() { + this.container.stop(); + } + } + + @Test + public void unboundIdContainerWhenMalformedLdifThenException() { + try { + appCtx = new AnnotationConfigApplicationContext(MalformedLdifConfig.class); + failBecauseExceptionWasNotThrown(IllegalStateException.class); + } catch (Exception e) { + assertThat(e.getCause()).isInstanceOf(IllegalStateException.class); + assertThat(e.getMessage()).contains("Unable to load LDIF classpath:test-server-malformed.txt"); + } + } + + @Configuration + static class MalformedLdifConfig { + private UnboundIdContainer container = new UnboundIdContainer("dc=springframework,dc=org", + "classpath:test-server-malformed.txt"); + + @Bean + UnboundIdContainer ldapContainer() { + this.container.setPort(0); + return this.container; + } + + @PreDestroy + void shutdown() { + this.container.stop(); + } + } +} diff --git a/ldap/src/integration-test/resources/test-server-malformed.txt b/ldap/src/integration-test/resources/test-server-malformed.txt new file mode 100644 index 0000000000..e24b511f76 --- /dev/null +++ b/ldap/src/integration-test/resources/test-server-malformed.txt @@ -0,0 +1,9 @@ +dn: ou=groups,dc=springframework,dc=org +objectclass: top +objectclass: organizationalUnit +ou: groups + +dn ou=subgroups,ou=groups,dc=springframework,dc=org +objectclass: top +objectclass: organizationalUnit +ou: subgroups diff --git a/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java b/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java index a533b029cb..aaa54ee413 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java +++ b/ldap/src/main/java/org/springframework/security/ldap/server/UnboundIdContainer.java @@ -114,10 +114,10 @@ public class UnboundIdContainer implements InitializingBean, DisposableBean, Lif private void importLdif(InMemoryDirectoryServer directoryServer) { if (StringUtils.hasText(this.ldif)) { - Resource resource = this.context.getResource(this.ldif); try { - if (resource.exists()) { - try (InputStream inputStream = resource.getInputStream()) { + Resource[] resources = this.context.getResources(this.ldif); + if (resources.length > 0 && resources[0].exists()) { + try (InputStream inputStream = resources[0].getInputStream()) { directoryServer.importFromLDIF(false, new LDIFReader(inputStream)); } } From 2dbedf7af5294fd8a2a641a8b644763e8e304cdb Mon Sep 17 00:00:00 2001 From: Peter Keller Date: Thu, 23 Jan 2020 15:34:35 +0100 Subject: [PATCH 022/348] Set charset of BasicAuthenticationFilter converter Allow BasicAuthenticationFilter to pick up the given credentials charset. Fixes: gh-7835 --- .../www/BasicAuthenticationFilter.java | 2 + .../www/BasicAuthenticationFilterTests.java | 106 +++++++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java index e5fc91940c..a2c442405a 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java @@ -17,6 +17,7 @@ package org.springframework.security.web.authentication.www; import java.io.IOException; +import java.nio.charset.Charset; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -276,6 +277,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter { public void setCredentialsCharset(String credentialsCharset) { Assert.hasText(credentialsCharset, "credentialsCharset cannot be null or empty"); this.credentialsCharset = credentialsCharset; + this.authenticationConverter.setCredentialsCharset(Charset.forName(credentialsCharset)); } protected String getCredentialsCharset(HttpServletRequest httpRequest) { diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java index 3b604a3cd1..c03b132807 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java @@ -16,13 +16,14 @@ package org.springframework.security.web.authentication.www; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import static org.mockito.AdditionalMatchers.not; import static org.mockito.Mockito.*; import javax.servlet.FilterChain; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.junit.After; @@ -40,6 +41,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.web.util.WebUtils; +import java.nio.charset.StandardCharsets; + /** * Tests {@link BasicAuthenticationFilter}. * @@ -320,4 +323,105 @@ public class BasicAuthenticationFilterTests { assertThat(response.getStatus()).isEqualTo(200); } + + @Test + public void doFilterWhenTokenAndFilterCharsetMatchDefaultThenAuthenticated() throws Exception { + SecurityContextHolder.clearContext(); + + UsernamePasswordAuthenticationToken rodRequest = new UsernamePasswordAuthenticationToken("rod", "Ă€Ă¶ĂŒ"); + rodRequest.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())); + Authentication rod = new UsernamePasswordAuthenticationToken("rod", "Ă€Ă¶ĂŒ", AuthorityUtils.createAuthorityList("ROLE_1")); + + manager = mock(AuthenticationManager.class); + when(manager.authenticate(rodRequest)).thenReturn(rod); + when(manager.authenticate(not(eq(rodRequest)))).thenThrow(new BadCredentialsException("")); + + filter = new BasicAuthenticationFilter(manager, new BasicAuthenticationEntryPoint()); + + String token = "rod:Ă€Ă¶ĂŒ"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes(StandardCharsets.UTF_8)))); + request.setServletPath("/some_file.html"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Test + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + FilterChain chain = mock(FilterChain.class); + + filter.doFilter(request, response, chain); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); + assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("rod"); + assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("Ă€Ă¶ĂŒ"); + } + + @Test + public void doFilterWhenTokenAndFilterCharsetMatchNonDefaultThenAuthenticated() throws Exception { + SecurityContextHolder.clearContext(); + + UsernamePasswordAuthenticationToken rodRequest = new UsernamePasswordAuthenticationToken("rod", "Ă€Ă¶ĂŒ"); + rodRequest.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())); + Authentication rod = new UsernamePasswordAuthenticationToken("rod", "Ă€Ă¶ĂŒ", AuthorityUtils.createAuthorityList("ROLE_1")); + + manager = mock(AuthenticationManager.class); + when(manager.authenticate(rodRequest)).thenReturn(rod); + when(manager.authenticate(not(eq(rodRequest)))).thenThrow(new BadCredentialsException("")); + + filter = new BasicAuthenticationFilter(manager, new BasicAuthenticationEntryPoint()); + filter.setCredentialsCharset("ISO-8859-1"); + + String token = "rod:Ă€Ă¶ĂŒ"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes(StandardCharsets.ISO_8859_1)))); + request.setServletPath("/some_file.html"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Test + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + FilterChain chain = mock(FilterChain.class); + + filter.doFilter(request, response, chain); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); + verify(chain).doFilter(any(ServletRequest.class), any(ServletResponse.class)); + assertThat(SecurityContextHolder.getContext().getAuthentication().getName()).isEqualTo("rod"); + assertThat(SecurityContextHolder.getContext().getAuthentication().getCredentials()).isEqualTo("Ă€Ă¶ĂŒ"); + } + + @Test + public void doFilterWhenTokenAndFilterCharsetDoNotMatchThenUnauthorized() throws Exception { + SecurityContextHolder.clearContext(); + + UsernamePasswordAuthenticationToken rodRequest = new UsernamePasswordAuthenticationToken("rod", "Ă€Ă¶ĂŒ"); + rodRequest.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest())); + Authentication rod = new UsernamePasswordAuthenticationToken("rod", "Ă€Ă¶ĂŒ", AuthorityUtils.createAuthorityList("ROLE_1")); + + manager = mock(AuthenticationManager.class); + when(manager.authenticate(rodRequest)).thenReturn(rod); + when(manager.authenticate(not(eq(rodRequest)))).thenThrow(new BadCredentialsException("")); + + filter = new BasicAuthenticationFilter(manager, new BasicAuthenticationEntryPoint()); + filter.setCredentialsCharset("ISO-8859-1"); + + String token = "rod:Ă€Ă¶ĂŒ"; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes(StandardCharsets.UTF_8)))); + request.setServletPath("/some_file.html"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + // Test + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + FilterChain chain = mock(FilterChain.class); + + filter.doFilter(request, response, chain); + + assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); + verify(chain, never()).doFilter(any(ServletRequest.class), any(ServletResponse.class)); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + } + } From edb6cd372978891bff7ac251b352f9c8c16df73d Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 27 Jan 2020 13:10:03 +0100 Subject: [PATCH 023/348] Fix authenticationFailureHandler not being used The custom server authenticationFailureHandler was not always picked up Fixes: gh-7782 --- .../config/web/server/ServerHttpSecurity.java | 4 ++- .../config/web/server/FormLoginTests.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index e395752cdd..195555b0d2 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -3050,7 +3050,9 @@ public class ServerHttpSecurity { this.defaultEntryPoint = new RedirectServerAuthenticationEntryPoint(loginPage); this.authenticationEntryPoint = this.defaultEntryPoint; this.requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, loginPage); - this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler(loginPage + "?error"); + if (this.authenticationFailureHandler == null) { + this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler(loginPage + "?error"); + } return this; } diff --git a/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java index 31c4ac5694..5fcae75750 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java @@ -33,6 +33,7 @@ import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverB import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; +import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.csrf.CsrfToken; @@ -213,6 +214,37 @@ public class FormLoginTests { homePage.assertAt(); } + @Test + public void formLoginWhenCustomAuthenticationFailureHandlerThenUsed() { + SecurityWebFilterChain securityWebFilter = this.http + .authorizeExchange() + .pathMatchers("/login", "/failure").permitAll() + .anyExchange().authenticated() + .and() + .formLogin() + .authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/failure")) + .and() + .build(); + + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .build(); + + WebDriver driver = WebTestClientHtmlUnitDriverBuilder + .webTestClientSetup(webTestClient) + .build(); + + DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class) + .assertAt(); + + loginPage.loginForm() + .username("invalid") + .password("invalid") + .submit(HomePage.class); + + assertThat(driver.getCurrentUrl()).endsWith("/failure"); + } + @Test public void authenticationSuccess() { SecurityWebFilterChain securityWebFilter = this.http From 9dd3dfe7182c16151007b96d2d21f5ee009a0e65 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 27 Jan 2020 16:11:44 +0100 Subject: [PATCH 024/348] Fix requiresAuthenticationMatcher not being used The custom server requiresAuthenticationMatcher was not always picked up Fixes: gh-7863 --- .../config/web/server/ServerHttpSecurity.java | 4 ++- .../config/web/server/FormLoginTests.java | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 195555b0d2..04fd40d84f 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -3049,7 +3049,9 @@ public class ServerHttpSecurity { public FormLoginSpec loginPage(String loginPage) { this.defaultEntryPoint = new RedirectServerAuthenticationEntryPoint(loginPage); this.authenticationEntryPoint = this.defaultEntryPoint; - this.requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, loginPage); + if (this.requiresAuthenticationMatcher == null) { + this.requiresAuthenticationMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, loginPage); + } if (this.authenticationFailureHandler == null) { this.authenticationFailureHandler = new RedirectServerAuthenticationFailureHandler(loginPage + "?error"); } diff --git a/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java index 5fcae75750..c0f1ff8938 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java @@ -37,6 +37,7 @@ import org.springframework.security.web.server.authentication.RedirectServerAuth import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; import org.springframework.security.web.server.context.ServerSecurityContextRepository; import org.springframework.security.web.server.csrf.CsrfToken; +import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; import org.springframework.stereotype.Controller; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.GetMapping; @@ -245,6 +246,31 @@ public class FormLoginTests { assertThat(driver.getCurrentUrl()).endsWith("/failure"); } + @Test + public void formLoginWhenCustomRequiresAuthenticationMatcherThenUsed() { + SecurityWebFilterChain securityWebFilter = this.http + .authorizeExchange() + .pathMatchers("/login", "/sign-in").permitAll() + .anyExchange().authenticated() + .and() + .formLogin() + .requiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/sign-in")) + .and() + .build(); + + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(securityWebFilter) + .build(); + + WebDriver driver = WebTestClientHtmlUnitDriverBuilder + .webTestClientSetup(webTestClient) + .build(); + + driver.get("http://localhost/sign-in"); + + assertThat(driver.getCurrentUrl()).endsWith("/login?error"); + } + @Test public void authenticationSuccess() { SecurityWebFilterChain securityWebFilter = this.http From ce6a0368bd5d87f50097e16e888315b772b9cd74 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 13:38:17 -0500 Subject: [PATCH 025/348] Update to Spring Framework 5.2.3 Fixes gh-7894 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 42f8578eec..f9c1e4758d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.1.RELEASE' + ext.springVersion = '5.2.3.RELEASE' } if (!project.hasProperty('springDataVersion')) { From dbc43fb47db4ee4ca71149e85f0c54a6c53e7eff Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 13:40:39 -0500 Subject: [PATCH 026/348] Update to Spring Data Moore SR3 Fixes gh-7895 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f9c1e4758d..1fd2dfd5ed 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR1' + ext.springDataVersion = 'Moore-SR3' } ext.rsocketVersion = '1.0.0-RC5' From 3cc4a945c628a2418f790a24e36e88882791dbeb Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 13:43:17 -0500 Subject: [PATCH 027/348] Update to Reactor Dysprosium SR4 Fixes gh-7896 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 1fd2dfd5ed..bc47fb7fcb 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR1' + ext.reactorVersion = 'Dysprosium-SR4' } if (!project.hasProperty('springVersion')) { From 9db3f51f2a964ff469fe0c50f0b3de5499a933f5 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:06:11 -0500 Subject: [PATCH 028/348] Update to Jackson 2.10.2 Fixes gh-7897 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index bc47fb7fcb..6f3b861894 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -42,9 +42,9 @@ dependencyManagement { dependency 'asm:asm:3.1' dependency 'ch.qos.logback:logback-classic:1.2.3' dependency 'ch.qos.logback:logback-core:1.2.3' - dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.0' - dependency 'com.fasterxml.jackson.core:jackson-core:2.10.0' - dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.0' + dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.2' + dependency 'com.fasterxml.jackson.core:jackson-core:2.10.2' + dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.2' dependency 'com.fasterxml:classmate:1.3.4' dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1' dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion' From 87ea0835207137c282697cf6a9c036c28030a441 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:24:11 -0500 Subject: [PATCH 029/348] Update to com.squareup.okhttp3 3.14.6 Fixes gh-7898 --- gradle/dependency-management.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 6f3b861894..09fc560fd9 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -20,7 +20,7 @@ dependencyManagement { } dependencies { dependency 'cglib:cglib-nodep:3.3.0' - dependency 'com.squareup.okhttp3:mockwebserver:3.14.2' + dependency 'com.squareup.okhttp3:mockwebserver:3.14.6' dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' @@ -58,7 +58,7 @@ dependencyManagement { dependency 'com.nimbusds:lang-tag:1.4.3' dependency 'com.nimbusds:nimbus-jose-jwt:7.8.1' dependency 'com.nimbusds:oauth2-oidc-sdk:6.14' - dependency 'com.squareup.okhttp3:okhttp:3.14.1' + dependency 'com.squareup.okhttp3:okhttp:3.14.6' dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-impl:2.3.2' From 6e0fbfcccd4c27b92f875271a48176daf878385d Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:31:31 -0500 Subject: [PATCH 030/348] Update to commons-codec 1.14 Fixes gh-7899 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 09fc560fd9..373adc347d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -65,7 +65,7 @@ dependencyManagement { dependency 'com.unboundid:unboundid-ldapsdk:4.0.12' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'commons-cli:commons-cli:1.4' - dependency 'commons-codec:commons-codec:1.13' + dependency 'commons-codec:commons-codec:1.14' dependency 'commons-collections:commons-collections:3.2.2' dependency 'commons-httpclient:commons-httpclient:3.1' dependency 'commons-io:commons-io:2.6' From 00b08bc7257ec6dcc6c76f021169b4da02599e63 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:39:27 -0500 Subject: [PATCH 031/348] Update to httpclient 4.5.11 Fixes gh-7903 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 373adc347d..ed93f3d10d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -138,7 +138,7 @@ dependencyManagement { dependency 'org.apache.directory.shared:shared-cursor:0.9.15' dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15' dependency 'org.apache.directory.shared:shared-ldap:0.9.15' - dependency 'org.apache.httpcomponents:httpclient:4.5.10' + dependency 'org.apache.httpcomponents:httpclient:4.5.11' dependency 'org.apache.httpcomponents:httpcore:4.4.8' dependency 'org.apache.httpcomponents:httpmime:4.5.3' dependency 'org.apache.mina:mina-core:2.0.0-M6' From 46486194c22f2fc245cfe02a17b5f140bed2cd7f Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:44:05 -0500 Subject: [PATCH 032/348] Update to org.aspectj 1.9.5 Fixes gh-7904 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7170ddd243..6696bc4fac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -aspectjVersion=1.9.3 +aspectjVersion=1.9.5 gaeVersion=1.9.76 springBootVersion=2.2.0.RELEASE version=5.2.2.BUILD-SNAPSHOT From 8054239a12e00e9b8f36b6eeb945c221af378edf Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:51:05 -0500 Subject: [PATCH 033/348] Update to hibernate-entitymanager 5.4.10.Final Fixes gh-7905 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ed93f3d10d..5cb210f173 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.17.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.8.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.10.Final' dependency 'org.hibernate:hibernate-validator:6.1.0.Final' dependency 'org.hsqldb:hsqldb:2.5.0' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From ea809b01a6bee20a730fc065bd8ba93e5efababc Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:53:08 -0500 Subject: [PATCH 034/348] Update to hibernate-validator 6.1.2.Final Fixes gh-7906 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 5cb210f173..2e9536b39e 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -183,7 +183,7 @@ dependencyManagement { dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.17.Final' dependency 'org.hibernate:hibernate-entitymanager:5.4.10.Final' - dependency 'org.hibernate:hibernate-validator:6.1.0.Final' + dependency 'org.hibernate:hibernate-validator:6.1.2.Final' dependency 'org.hsqldb:hsqldb:2.5.0' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.javassist:javassist:3.22.0-CR2' From 9e6910273c835b438939b4d719ed8db012cddbcc Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 14:56:28 -0500 Subject: [PATCH 035/348] Update to org.powermock 2.0.5 Fixes gh-7907 --- gradle/dependency-management.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 2e9536b39e..de2d2d9675 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -24,12 +24,12 @@ dependencyManagement { dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' - dependency 'org.powermock:powermock-api-mockito2:2.0.4' - dependency 'org.powermock:powermock-api-support:2.0.4' - dependency 'org.powermock:powermock-core:2.0.4' - dependency 'org.powermock:powermock-module-junit4-common:2.0.4' - dependency 'org.powermock:powermock-module-junit4:2.0.4' - dependency 'org.powermock:powermock-reflect:2.0.4' + dependency 'org.powermock:powermock-api-mockito2:2.0.5' + dependency 'org.powermock:powermock-api-support:2.0.5' + dependency 'org.powermock:powermock-core:2.0.5' + dependency 'org.powermock:powermock-module-junit4-common:2.0.5' + dependency 'org.powermock:powermock-module-junit4:2.0.5' + dependency 'org.powermock:powermock-reflect:2.0.5' dependency 'org.python:jython:2.5.0' dependency 'org.spockframework:spock-core:1.0-groovy-2.4' dependency 'org.spockframework:spock-spring:1.0-groovy-2.4' From a5b6b9a3982ba3aeee97e354118176b7540a2291 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 15:04:46 -0500 Subject: [PATCH 036/348] Update to org.slf4j 1.7.30 Fixes gh-7908 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index de2d2d9675..5d2330178f 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -203,11 +203,11 @@ dependencyManagement { dependency 'org.seleniumhq.selenium:selenium-support:3.141.59' dependency 'org.seleniumhq.selenium:selenium-api:3.141.59' dependency 'org.skyscreamer:jsonassert:1.5.0' - dependency 'org.slf4j:jcl-over-slf4j:1.7.28' - dependency 'org.slf4j:jul-to-slf4j:1.7.28' - dependency 'org.slf4j:log4j-over-slf4j:1.7.28' - dependency 'org.slf4j:slf4j-api:1.7.28' - dependency 'org.slf4j:slf4j-nop:1.7.28' + dependency 'org.slf4j:jcl-over-slf4j:1.7.30' + dependency 'org.slf4j:jul-to-slf4j:1.7.30' + dependency 'org.slf4j:log4j-over-slf4j:1.7.30' + dependency 'org.slf4j:slf4j-api:1.7.30' + dependency 'org.slf4j:slf4j-nop:1.7.30' dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' From 6c310213a8f178ada643019524adb7248a086fa4 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 4 Feb 2020 15:07:16 -0500 Subject: [PATCH 037/348] Update to Spring Boot 2.2.4 Fixes gh-7909 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 6696bc4fac..bf7547d98b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.76 -springBootVersion=2.2.0.RELEASE +springBootVersion=2.2.4.RELEASE version=5.2.2.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From c4ccc96655117b9137247aba5b51da8ebfba6778 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 4 Feb 2020 17:26:29 -0700 Subject: [PATCH 038/348] Polish Error Messages for OpaqueTokenIntrospectors --- .../server/resource/OAuth2ResourceServerConfigurerTests.java | 2 +- .../resource/introspection/NimbusOpaqueTokenIntrospector.java | 4 ++-- .../introspection/NimbusReactiveOpaqueTokenIntrospector.java | 2 +- .../introspection/NimbusOpaqueTokenIntrospectorTests.java | 2 +- .../NimbusReactiveOpaqueTokenIntrospectorTests.java | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 8914c8e260..b516954f8e 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -1127,7 +1127,7 @@ public class OAuth2ResourceServerConfigurerTests { .with(bearerToken("token"))) .andExpect(status().isUnauthorized()) .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - containsString("Provided token [token] isn't active"))); + containsString("Provided token isn't active"))); } @Test diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java index c4b0569ada..642dc451f1 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java @@ -133,7 +133,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { public OAuth2AuthenticatedPrincipal introspect(String token) { RequestEntity requestEntity = this.requestEntityConverter.convert(token); if (requestEntity == null) { - throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active"); + throw new OAuth2IntrospectionException("requestEntityConverter returned a null entity"); } ResponseEntity responseEntity = makeRequest(requestEntity); @@ -143,7 +143,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { // relying solely on the authorization server to validate this token (not checking 'exp', for example) if (!introspectionSuccessResponse.isActive()) { - throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active"); + throw new OAuth2IntrospectionException("Provided token isn't active"); } return convertClaimsSet(introspectionSuccessResponse); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java index 9908979d28..91690f8f10 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java @@ -154,7 +154,7 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke private void validate(String token, TokenIntrospectionSuccessResponse response) { // relying solely on the authorization server to validate this token (not checking 'exp', for example) if (!response.isActive()) { - throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active"); + throw new OAuth2IntrospectionException("Provided token isn't active"); } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java index 966d8eae62..2ebf11c8b5 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java @@ -168,7 +168,7 @@ public class NimbusOpaqueTokenIntrospectorTests { assertThatCode(() -> introspectionClient.introspect("token")) .isInstanceOf(OAuth2IntrospectionException.class) .extracting("message") - .containsExactly("Provided token [token] isn't active"); + .containsExactly("Provided token isn't active"); } @Test diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java index 0b58f45e6b..7454debbaf 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java @@ -142,7 +142,7 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests { assertThatCode(() -> introspectionClient.introspect("token").block()) .isInstanceOf(OAuth2IntrospectionException.class) .extracting("message") - .containsExactly("Provided token [token] isn't active"); + .containsExactly("Provided token isn't active"); } @Test From 9a2b71d931d3b639567fab4a50f1ad2fffa46961 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 5 Feb 2020 10:27:57 -0500 Subject: [PATCH 039/348] Release 5.2.2.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bf7547d98b..e651d8ecd6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.76 springBootVersion=2.2.4.RELEASE -version=5.2.2.BUILD-SNAPSHOT +version=5.2.2.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 1da8e9df132fe4079aa34bc56cc74a40c0530ccf Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 5 Feb 2020 11:03:09 -0500 Subject: [PATCH 040/348] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e651d8ecd6..10ee0508ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.76 springBootVersion=2.2.4.RELEASE -version=5.2.2.RELEASE +version=5.2.3.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 2dc8147106526c00243069a7c7af49cc672b7c7c Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 5 Feb 2020 15:18:32 -0500 Subject: [PATCH 041/348] Add release-notes-sections.yml --- scripts/release/release-notes-sections.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 scripts/release/release-notes-sections.yml diff --git a/scripts/release/release-notes-sections.yml b/scripts/release/release-notes-sections.yml new file mode 100644 index 0000000000..574c8cfbec --- /dev/null +++ b/scripts/release/release-notes-sections.yml @@ -0,0 +1,14 @@ +releasenotes: + sections: + - title: "New Features" + emoji: ":star:" + labels: ["enhancement"] + - title: "Bug Fixes" + emoji: ":beetle:" + labels: ["bug", "regression"] + - title: "Dependency Upgrades" + emoji: ":hammer:" + labels: ["dependency-upgrade"] + - title: "Non-passive" + emoji: ":rewind:" + labels: ["breaks-passivity"] From 0012e24c46872e2159beeee41b0f024beeee9e7f Mon Sep 17 00:00:00 2001 From: Stephane Maldini Date: Thu, 6 Feb 2020 14:16:38 -0800 Subject: [PATCH 042/348] Don't force downcasting of RequestAttributes to ServletRequestAttributes Fixes gh-7953 --- .../SecurityReactorContextConfiguration.java | 30 ++++------- ...urityReactorContextConfigurationTests.java | 50 ++++++++++++++++++- .../DefaultOAuth2AuthorizedClientManager.java | 15 +++--- ...uthorizedClientExchangeFilterFunction.java | 15 +++--- 4 files changed, 73 insertions(+), 37 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfiguration.java index a4e8fe0df6..8d76982c80 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -23,6 +23,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import reactor.core.CoreSubscriber; @@ -92,32 +93,21 @@ class SecurityReactorContextConfiguration { } private static boolean contextAttributesAvailable() { - HttpServletRequest servletRequest = null; - HttpServletResponse servletResponse = null; - ServletRequestAttributes requestAttributes = - (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (requestAttributes != null) { - servletRequest = requestAttributes.getRequest(); - servletResponse = requestAttributes.getResponse(); - } - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null || servletRequest != null || servletResponse != null) { - return true; - } - return false; + return SecurityContextHolder.getContext().getAuthentication() != null || + RequestContextHolder.getRequestAttributes() instanceof ServletRequestAttributes; } private static Map getContextAttributes() { HttpServletRequest servletRequest = null; HttpServletResponse servletResponse = null; - ServletRequestAttributes requestAttributes = - (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (requestAttributes != null) { - servletRequest = requestAttributes.getRequest(); - servletResponse = requestAttributes.getResponse(); + RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); + if (requestAttributes instanceof ServletRequestAttributes) { + ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; + servletRequest = servletRequestAttributes.getRequest(); + servletResponse = servletRequestAttributes.getResponse(); // possible null } Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null && servletRequest == null && servletResponse == null) { + if (authentication == null && servletRequest == null) { return Collections.emptyMap(); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationTests.java index b26cef84b7..11d8317dde 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/SecurityReactorContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -28,6 +28,7 @@ import org.springframework.security.config.test.SpringTestRule; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.client.web.reactive.function.client.MockExchangeFunction; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.reactive.function.client.ClientRequest; @@ -36,6 +37,7 @@ import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import reactor.core.CoreSubscriber; import reactor.core.publisher.BaseSubscriber; import reactor.core.publisher.Mono; +import reactor.core.publisher.Operators; import reactor.test.StepVerifier; import reactor.util.context.Context; @@ -139,6 +141,52 @@ public class SecurityReactorContextConfigurationTests { assertThat(resultContext).isSameAs(parentContext); } + @Test + public void createSubscriberIfNecessaryWhenNotServletRequestAttributesThenStillCreate() { + RequestContextHolder.setRequestAttributes( + new RequestAttributes() { + @Override + public Object getAttribute(String name, int scope) { + return null; + } + + @Override + public void setAttribute(String name, Object value, int scope) { + } + + @Override + public void removeAttribute(String name, int scope) { + } + + @Override + public String[] getAttributeNames(int scope) { + return new String[0]; + } + + @Override + public void registerDestructionCallback(String name, Runnable callback, int scope) { + } + + @Override + public Object resolveReference(String key) { + return null; + } + + @Override + public String getSessionId() { + return null; + } + + @Override + public Object getSessionMutex() { + return null; + } + }); + + CoreSubscriber subscriber = this.subscriberRegistrar.createSubscriberIfNecessary(Operators.emptySubscriber()); + assertThat(subscriber).isInstanceOf(SecurityReactorContextConfiguration.SecurityReactorContextSubscriber.class); + } + @Test public void createPublisherWhenLastOperatorAddedThenSecurityContextAttributesAvailable() { // Trigger the importing of SecurityReactorContextConfiguration via OAuth2ImportSelector diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java index 19719dc7c4..85b614c070 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizedClientManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -28,6 +28,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -121,9 +122,9 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori private static HttpServletRequest getHttpServletRequestOrDefault(Map attributes) { HttpServletRequest servletRequest = (HttpServletRequest) attributes.get(HttpServletRequest.class.getName()); if (servletRequest == null) { - ServletRequestAttributes context = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (context != null) { - servletRequest = context.getRequest(); + RequestAttributes context = RequestContextHolder.getRequestAttributes(); + if (context instanceof ServletRequestAttributes) { + servletRequest = ((ServletRequestAttributes) context).getRequest(); } } return servletRequest; @@ -132,9 +133,9 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori private static HttpServletResponse getHttpServletResponseOrDefault(Map attributes) { HttpServletResponse servletResponse = (HttpServletResponse) attributes.get(HttpServletResponse.class.getName()); if (servletResponse == null) { - ServletRequestAttributes context = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (context != null) { - servletResponse = context.getResponse(); + RequestAttributes context = RequestContextHolder.getRequestAttributes(); + if (context instanceof ServletRequestAttributes) { + servletResponse = ((ServletRequestAttributes) context).getResponse(); } } return servletResponse; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java index 147e3255c0..22d488c965 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServletOAuth2AuthorizedClientExchangeFilterFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -36,6 +36,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.util.Assert; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.reactive.function.client.ClientRequest; @@ -389,15 +390,11 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement attrs.containsKey(HTTP_SERVLET_RESPONSE_ATTR_NAME)) { return; } - ServletRequestAttributes context = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = null; - HttpServletResponse response = null; - if (context != null) { - request = context.getRequest(); - response = context.getResponse(); + RequestAttributes context = RequestContextHolder.getRequestAttributes(); + if (context instanceof ServletRequestAttributes) { + attrs.putIfAbsent(HTTP_SERVLET_REQUEST_ATTR_NAME, ((ServletRequestAttributes) context).getRequest()); + attrs.putIfAbsent(HTTP_SERVLET_RESPONSE_ATTR_NAME, ((ServletRequestAttributes) context).getResponse()); } - attrs.putIfAbsent(HTTP_SERVLET_REQUEST_ATTR_NAME, request); - attrs.putIfAbsent(HTTP_SERVLET_RESPONSE_ATTR_NAME, response); } private void populateDefaultAuthentication(Map attrs) { From 1e4736f9b37566663f0e5c94bda2f961e0104fb6 Mon Sep 17 00:00:00 2001 From: Manuel Bleichenbacher Date: Thu, 30 Jan 2020 20:31:12 +0100 Subject: [PATCH 043/348] Prevent double-escaping of authorize URL parameters If the authorization URL in the OAuth2 provider configuration contained query parameters with escaped characters, these characters were escaped a second time. This commit fixes it. It is relevant to support the OIDC claims parameter (see https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter). Fixes gh-7871 --- .../endpoint/OAuth2AuthorizationRequest.java | 22 +++++++----- .../OAuth2AuthorizationRequestTests.java | 36 ++++++++++++++++++- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java index 5c10438d1b..f6490355ea 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -23,6 +23,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; import java.io.Serializable; import java.nio.charset.StandardCharsets; @@ -376,29 +377,34 @@ public final class OAuth2AuthorizationRequest implements Serializable { private String buildAuthorizationRequestUri() { MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, this.responseType.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, this.clientId); + parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, encodeQueryParam(this.responseType.getValue())); + parameters.set(OAuth2ParameterNames.CLIENT_ID, encodeQueryParam(this.clientId)); if (!CollectionUtils.isEmpty(this.scopes)) { parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(this.scopes, " ")); + encodeQueryParam(StringUtils.collectionToDelimitedString(this.scopes, " "))); } if (this.state != null) { - parameters.set(OAuth2ParameterNames.STATE, this.state); + parameters.set(OAuth2ParameterNames.STATE, encodeQueryParam(this.state)); } if (this.redirectUri != null) { - parameters.set(OAuth2ParameterNames.REDIRECT_URI, this.redirectUri); + parameters.set(OAuth2ParameterNames.REDIRECT_URI, encodeQueryParam(this.redirectUri)); } if (!CollectionUtils.isEmpty(this.additionalParameters)) { - this.additionalParameters.forEach((k, v) -> parameters.set(k, v.toString())); + this.additionalParameters.forEach((k, v) -> + parameters.set(encodeQueryParam(k), encodeQueryParam(v.toString()))); } return UriComponentsBuilder.fromHttpUrl(this.authorizationUri) .queryParams(parameters) - .encode(StandardCharsets.UTF_8) .build() .toUriString(); } + // Encode query parameter value according to RFC 3986 + private static String encodeQueryParam(String value) { + return UriUtils.encodeQueryParam(value, StandardCharsets.UTF_8); + } + private LinkedHashSet toLinkedHashSet(String... scope) { LinkedHashSet result = new LinkedHashSet<>(); Collections.addAll(result, scope); diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java index caa1da5fbe..6480442f53 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -307,4 +307,38 @@ public class OAuth2AuthorizationRequestTests { "response_type=code&client_id=client-id&state=state&" + "redirect_uri=https://example.com/authorize/oauth2/code/registration-id"); } + + @Test + public void buildWhenAuthorizationUriIncludesEscapedQueryParameterThenAuthorizationRequestUrlIncludesIt() { + OAuth2AuthorizationRequest authorizationRequest = + TestOAuth2AuthorizationRequests.request() + .authorizationUri(AUTHORIZATION_URI + + "?claims=%7B%22userinfo%22%3A%7B%22email_verified%22%3A%7B%22essential%22%3Atrue%7D%7D%7D").build(); + + assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull(); + assertThat(authorizationRequest.getAuthorizationRequestUri()) + .isEqualTo("https://provider.com/oauth2/authorize?" + + "claims=%7B%22userinfo%22%3A%7B%22email_verified%22%3A%7B%22essential%22%3Atrue%7D%7D%7D&" + + "response_type=code&client_id=client-id&state=state&" + + "redirect_uri=https://example.com/authorize/oauth2/code/registration-id"); + } + + @Test + public void buildWhenNonAsciiAdditionalParametersThenProperlyEncoded() { + Map additionalParameters = new HashMap<>(); + additionalParameters.put("item amount", "19.95" + '\u20ac'); + additionalParameters.put("item name", "H" + '\u00c5' + "M" + '\u00d6'); + additionalParameters.put('\u00e2' + "ge", "4" + '\u00bd'); + OAuth2AuthorizationRequest authorizationRequest = + TestOAuth2AuthorizationRequests.request() + .additionalParameters(additionalParameters) + .build(); + + assertThat(authorizationRequest.getAuthorizationRequestUri()).isNotNull(); + assertThat(authorizationRequest.getAuthorizationRequestUri()) + .isEqualTo("https://example.com/login/oauth/authorize?" + + "response_type=code&client_id=client-id&state=state&" + + "redirect_uri=https://example.com/authorize/oauth2/code/registration-id&" + + "item%20amount=19.95%E2%82%AC&%C3%A2ge=4%C2%BD&item%20name=H%C3%85M%C3%96"); + } } From cc7ea4acd3fb860d68e7e7871f7cc7427189a023 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 11 Nov 2019 06:36:02 -0500 Subject: [PATCH 044/348] OAuth2AuthorizationCodeGrantFilter matches on query parameters Fixes gh-7963 --- .../OAuth2AuthorizationCodeGrantFilter.java | 48 ++- ...uth2AuthorizationCodeGrantFilterTests.java | 307 +++++++++++------- 2 files changed, 221 insertions(+), 134 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java index 3eda6df7c6..4f8aaefbaf 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -41,6 +41,7 @@ import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.FilterChain; @@ -48,6 +49,11 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; /** * A {@code Filter} for the OAuth 2.0 Authorization Code Grant, @@ -132,24 +138,39 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - if (this.shouldProcessAuthorizationResponse(request)) { - this.processAuthorizationResponse(request, response); + if (matchesAuthorizationResponse(request)) { + processAuthorizationResponse(request, response); return; } filterChain.doFilter(request, response); } - private boolean shouldProcessAuthorizationResponse(HttpServletRequest request) { + private boolean matchesAuthorizationResponse(HttpServletRequest request) { + MultiValueMap params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap()); + if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) { + return false; + } OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.loadAuthorizationRequest(request); if (authorizationRequest == null) { return false; } - String requestUrl = UrlUtils.buildFullRequestUrl(request.getScheme(), request.getServerName(), - request.getServerPort(), request.getRequestURI(), null); - MultiValueMap params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap()); - if (requestUrl.equals(authorizationRequest.getRedirectUri()) && - OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) { + + // Compare redirect_uri + UriComponents requestUri = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request)).build(); + UriComponents redirectUri = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri()).build(); + Set>> requestUriParameters = new LinkedHashSet<>(requestUri.getQueryParams().entrySet()); + Set>> redirectUriParameters = new LinkedHashSet<>(redirectUri.getQueryParams().entrySet()); + // Remove the additional request parameters (if any) from the authorization response (request) + // before doing an exact comparison with the authorizationRequest.getRedirectUri() parameters (if any) + requestUriParameters.retainAll(redirectUriParameters); + + if (Objects.equals(requestUri.getScheme(), redirectUri.getScheme()) && + Objects.equals(requestUri.getUserInfo(), redirectUri.getUserInfo()) && + Objects.equals(requestUri.getHost(), redirectUri.getHost()) && + Objects.equals(requestUri.getPort(), redirectUri.getPort()) && + Objects.equals(requestUri.getPath(), redirectUri.getPath()) && + Objects.equals(requestUriParameters.toString(), redirectUriParameters.toString())) { return true; } return false; @@ -165,10 +186,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); MultiValueMap params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap()); - String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) - .replaceQuery(null) - .build() - .toUriString(); + String redirectUri = UrlUtils.buildFullRequestUrl(request); OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params, redirectUri); OAuth2AuthorizationCodeAuthenticationToken authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken( @@ -183,7 +201,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { } catch (OAuth2AuthorizationException ex) { OAuth2Error error = ex.getError(); UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromUriString(authorizationResponse.getRedirectUri()) + .fromUriString(authorizationRequest.getRedirectUri()) .queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode()); if (!StringUtils.isEmpty(error.getDescription())) { uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription()); @@ -206,7 +224,7 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter { this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request, response); - String redirectUrl = authorizationResponse.getRedirectUri(); + String redirectUrl = authorizationRequest.getRedirectUri(); SavedRequest savedRequest = this.requestCache.getRequest(request, response); if (savedRequest != null) { redirectUrl = savedRequest.getRedirectUrl(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java index aaacad6b21..39b3011f03 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.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. @@ -15,17 +15,9 @@ */ package org.springframework.security.oauth2.client.web; -import java.util.HashMap; -import java.util.Map; -import javax.servlet.FilterChain; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - import org.junit.After; import org.junit.Before; import org.junit.Test; - import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AnonymousAuthenticationToken; @@ -50,13 +42,26 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.util.CollectionUtils; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes; import static org.springframework.security.oauth2.core.TestOAuth2RefreshTokens.refreshToken; @@ -131,8 +136,7 @@ public class OAuth2AuthorizationCodeGrantFilterTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); // NOTE: A valid Authorization Response contains either a 'code' or 'error' parameter. - - HttpServletResponse response = mock(HttpServletResponse.class); + MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); this.filter.doFilter(request, response, filterChain); @@ -142,94 +146,142 @@ public class OAuth2AuthorizationCodeGrantFilterTests { @Test public void doFilterWhenAuthorizationRequestNotFoundThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - - HttpServletResponse response = mock(HttpServletResponse.class); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationResponseUrlDoesNotMatchAuthorizationRequestRedirectUriThenNotProcessed() throws Exception { - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - - HttpServletResponse response = mock(HttpServletResponse.class); - FilterChain filterChain = mock(FilterChain.class); - - this.setUpAuthorizationRequest(request, response, this.registration1); - request.setRequestURI(requestUri + "-no-match"); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void doFilterWhenAuthorizationResponseValidThenAuthorizationRequestRemoved() throws Exception { - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/path"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); - this.setUpAuthorizationRequest(request, response, this.registration1); + this.filter.doFilter(authorizationResponse, response, filterChain); + + verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + @Test + public void doFilterWhenAuthorizationRequestRedirectUriDoesNotMatchThenNotProcessed() throws Exception { + String requestUri = "/callback/client-1"; + MockHttpServletRequest authorizationRequest = createAuthorizationRequest(requestUri); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + MockHttpServletResponse response = new MockHttpServletResponse(); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); + authorizationResponse.setRequestURI(requestUri + "-no-match"); + FilterChain filterChain = mock(FilterChain.class); + + this.filter.doFilter(authorizationResponse, response, filterChain); + + verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + // gh-7963 + @Test + public void doFilterWhenAuthorizationRequestRedirectUriParametersMatchThenProcessed() throws Exception { + // 1) redirect_uri with query parameters + String requestUri = "/callback/client-1"; + Map parameters = new LinkedHashMap<>(); + parameters.put("param1", "value1"); + parameters.put("param2", "value2"); + MockHttpServletRequest authorizationRequest = createAuthorizationRequest(requestUri, parameters); + MockHttpServletResponse response = new MockHttpServletResponse(); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); + this.setUpAuthenticationResult(this.registration1); + FilterChain filterChain = mock(FilterChain.class); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + this.filter.doFilter(authorizationResponse, response, filterChain); + verifyNoInteractions(filterChain); + + // 2) redirect_uri with query parameters AND authorization response additional parameters + Map additionalParameters = new LinkedHashMap<>(); + additionalParameters.put("auth-param1", "value1"); + additionalParameters.put("auth-param2", "value2"); + response = new MockHttpServletResponse(); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); + authorizationResponse = createAuthorizationResponse(authorizationRequest, additionalParameters); + this.filter.doFilter(authorizationResponse, response, filterChain); + verifyNoInteractions(filterChain); + } + + // gh-7963 + @Test + public void doFilterWhenAuthorizationRequestRedirectUriParametersDoesNotMatchThenNotProcessed() throws Exception { + String requestUri = "/callback/client-1"; + Map parameters = new LinkedHashMap<>(); + parameters.put("param1", "value1"); + parameters.put("param2", "value2"); + MockHttpServletRequest authorizationRequest = createAuthorizationRequest(requestUri, parameters); + MockHttpServletResponse response = new MockHttpServletResponse(); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); + this.setUpAuthenticationResult(this.registration1); + FilterChain filterChain = mock(FilterChain.class); + + // 1) Parameter value + Map parametersNotMatch = new LinkedHashMap<>(parameters); + parametersNotMatch.put("param2", "value8"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse( + createAuthorizationRequest(requestUri, parametersNotMatch)); + authorizationResponse.setSession(authorizationRequest.getSession()); + this.filter.doFilter(authorizationResponse, response, filterChain); + verify(filterChain, times(1)).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + + // 2) Parameter order + parametersNotMatch = new LinkedHashMap<>(); + parametersNotMatch.put("param2", "value2"); + parametersNotMatch.put("param1", "value1"); + authorizationResponse = createAuthorizationResponse( + createAuthorizationRequest(requestUri, parametersNotMatch)); + authorizationResponse.setSession(authorizationRequest.getSession()); + this.filter.doFilter(authorizationResponse, response, filterChain); + verify(filterChain, times(2)).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + + // 3) Parameter missing + parametersNotMatch = new LinkedHashMap<>(parameters); + parametersNotMatch.remove("param2"); + authorizationResponse = createAuthorizationResponse( + createAuthorizationRequest(requestUri, parametersNotMatch)); + authorizationResponse.setSession(authorizationRequest.getSession()); + this.filter.doFilter(authorizationResponse, response, filterChain); + verify(filterChain, times(3)).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + @Test + public void doFilterWhenAuthorizationRequestMatchThenAuthorizationRequestRemoved() throws Exception { + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); this.setUpAuthenticationResult(this.registration1); - this.filter.doFilter(request, response, filterChain); + this.filter.doFilter(authorizationResponse, response, filterChain); - assertThat(this.authorizationRequestRepository.loadAuthorizationRequest(request)).isNull(); + assertThat(this.authorizationRequestRepository.loadAuthorizationRequest(authorizationResponse)).isNull(); } @Test public void doFilterWhenAuthorizationFailsThenHandleOAuth2AuthorizationException() throws Exception { - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); - this.setUpAuthorizationRequest(request, response, this.registration1); OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_GRANT); when(this.authenticationManager.authenticate(any(Authentication.class))) .thenThrow(new OAuth2AuthorizationException(error)); - this.filter.doFilter(request, response, filterChain); + this.filter.doFilter(authorizationResponse, response, filterChain); assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1?error=invalid_grant"); } @Test - public void doFilterWhenAuthorizationResponseSuccessThenAuthorizedClientSavedToService() throws Exception { - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - + public void doFilterWhenAuthorizationSucceedsThenAuthorizedClientSavedToService() throws Exception { + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); - - this.setUpAuthorizationRequest(request, response, this.registration1); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); this.setUpAuthenticationResult(this.registration1); - this.filter.doFilter(request, response, filterChain); + this.filter.doFilter(authorizationResponse, response, filterChain); OAuth2AuthorizedClient authorizedClient = this.authorizedClientService.loadAuthorizedClient( this.registration1.getRegistrationId(), this.principalName1); @@ -241,40 +293,31 @@ public class OAuth2AuthorizationCodeGrantFilterTests { } @Test - public void doFilterWhenAuthorizationResponseSuccessThenRedirected() throws Exception { - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - + public void doFilterWhenAuthorizationSucceedsThenRedirected() throws Exception { + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); - - this.setUpAuthorizationRequest(request, response, this.registration1); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); this.setUpAuthenticationResult(this.registration1); - this.filter.doFilter(request, response, filterChain); + this.filter.doFilter(authorizationResponse, response, filterChain); assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/callback/client-1"); } @Test - public void doFilterWhenAuthorizationResponseSuccessHasSavedRequestThenRedirectedToSavedRequest() throws Exception { + public void doFilterWhenAuthorizationSucceedsAndHasSavedRequestThenRedirectToSavedRequest() throws Exception { String requestUri = "/saved-request"; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); MockHttpServletResponse response = new MockHttpServletResponse(); RequestCache requestCache = new HttpSessionRequestCache(); requestCache.saveRequest(request, response); - - requestUri = "/callback/client-1"; - request.setRequestURI(requestUri); + request.setRequestURI("/callback/client-1"); request.addParameter(OAuth2ParameterNames.CODE, "code"); request.addParameter(OAuth2ParameterNames.STATE, "state"); - FilterChain filterChain = mock(FilterChain.class); - this.setUpAuthorizationRequest(request, response, this.registration1); this.setUpAuthenticationResult(this.registration1); @@ -284,36 +327,30 @@ public class OAuth2AuthorizationCodeGrantFilterTests { } @Test - public void doFilterWhenAuthorizationResponseSuccessAndAnonymousAccessThenAuthorizedClientSavedToHttpSession() throws Exception { + public void doFilterWhenAuthorizationSucceedsAndAnonymousAccessThenAuthorizedClientSavedToHttpSession() throws Exception { AnonymousAuthenticationToken anonymousPrincipal = new AnonymousAuthenticationToken("key-1234", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(anonymousPrincipal); SecurityContextHolder.setContext(securityContext); - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); - - this.setUpAuthorizationRequest(request, response, this.registration1); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); this.setUpAuthenticationResult(this.registration1); - this.filter.doFilter(request, response, filterChain); + this.filter.doFilter(authorizationResponse, response, filterChain); OAuth2AuthorizedClient authorizedClient = this.authorizedClientRepository.loadAuthorizedClient( - this.registration1.getRegistrationId(), anonymousPrincipal, request); + this.registration1.getRegistrationId(), anonymousPrincipal, authorizationResponse); assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1); assertThat(authorizedClient.getPrincipalName()).isEqualTo(anonymousPrincipal.getName()); assertThat(authorizedClient.getAccessToken()).isNotNull(); - HttpSession session = request.getSession(false); + HttpSession session = authorizationResponse.getSession(false); assertThat(session).isNotNull(); @SuppressWarnings("unchecked") @@ -325,33 +362,27 @@ public class OAuth2AuthorizationCodeGrantFilterTests { } @Test - public void doFilterWhenAuthorizationResponseSuccessAndAnonymousAccessNullAuthenticationThenAuthorizedClientSavedToHttpSession() throws Exception { + public void doFilterWhenAuthorizationSucceedsAndAnonymousAccessNullAuthenticationThenAuthorizedClientSavedToHttpSession() throws Exception { SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); SecurityContextHolder.setContext(securityContext); // null Authentication - String requestUri = "/callback/client-1"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.addParameter(OAuth2ParameterNames.CODE, "code"); - request.addParameter(OAuth2ParameterNames.STATE, "state"); - + MockHttpServletRequest authorizationRequest = createAuthorizationRequest("/callback/client-1"); + MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); - - this.setUpAuthorizationRequest(request, response, this.registration1); + this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); this.setUpAuthenticationResult(this.registration1); - this.filter.doFilter(request, response, filterChain); + this.filter.doFilter(authorizationResponse, response, filterChain); OAuth2AuthorizedClient authorizedClient = this.authorizedClientRepository.loadAuthorizedClient( - this.registration1.getRegistrationId(), null, request); + this.registration1.getRegistrationId(), null, authorizationResponse); assertThat(authorizedClient).isNotNull(); - assertThat(authorizedClient.getClientRegistration()).isEqualTo(this.registration1); assertThat(authorizedClient.getPrincipalName()).isEqualTo("anonymousUser"); assertThat(authorizedClient.getAccessToken()).isNotNull(); - HttpSession session = request.getSession(false); + HttpSession session = authorizationResponse.getSession(false); assertThat(session).isNotNull(); @SuppressWarnings("unchecked") @@ -362,13 +393,51 @@ public class OAuth2AuthorizationCodeGrantFilterTests { assertThat(authorizedClients.values().iterator().next()).isSameAs(authorizedClient); } + private static MockHttpServletRequest createAuthorizationRequest(String requestUri) { + return createAuthorizationRequest(requestUri, new LinkedHashMap<>()); + } + + private static MockHttpServletRequest createAuthorizationRequest(String requestUri, Map parameters) { + MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); + request.setServletPath(requestUri); + if (!CollectionUtils.isEmpty(parameters)) { + parameters.forEach(request::addParameter); + request.setQueryString( + parameters.entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .collect(Collectors.joining("&"))); + } + return request; + } + + private static MockHttpServletRequest createAuthorizationResponse(MockHttpServletRequest authorizationRequest) { + return createAuthorizationResponse(authorizationRequest, new LinkedHashMap<>()); + } + + private static MockHttpServletRequest createAuthorizationResponse( + MockHttpServletRequest authorizationRequest, Map additionalParameters) { + MockHttpServletRequest authorizationResponse = new MockHttpServletRequest( + authorizationRequest.getMethod(), authorizationRequest.getRequestURI()); + authorizationResponse.setServletPath(authorizationRequest.getRequestURI()); + authorizationRequest.getParameterMap().forEach(authorizationResponse::addParameter); + authorizationResponse.addParameter(OAuth2ParameterNames.CODE, "code"); + authorizationResponse.addParameter(OAuth2ParameterNames.STATE, "state"); + additionalParameters.forEach(authorizationResponse::addParameter); + authorizationResponse.setQueryString( + authorizationResponse.getParameterMap().entrySet().stream() + .map(e -> e.getKey() + "=" + e.getValue()[0]) + .collect(Collectors.joining("&"))); + authorizationResponse.setSession(authorizationRequest.getSession()); + return authorizationResponse; + } + private void setUpAuthorizationRequest(HttpServletRequest request, HttpServletResponse response, ClientRegistration registration) { - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId()); + Map attributes = new HashMap<>(); + attributes.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId()); OAuth2AuthorizationRequest authorizationRequest = request() - .additionalParameters(additionalParameters) - .redirectUri(request.getRequestURL().toString()).build(); + .attributes(attributes) + .redirectUri(UrlUtils.buildFullRequestUrl(request)).build(); this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); } From 6141132cfa2e321ea1cef844422f81e38191119a Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 10 Feb 2020 05:53:00 -0500 Subject: [PATCH 045/348] Fix test gh-7963 --- .../client/web/OAuth2AuthorizationCodeGrantFilterTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java index 39b3011f03..7c5ae8e385 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/OAuth2AuthorizationCodeGrantFilterTests.java @@ -61,7 +61,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.noScopes; import static org.springframework.security.oauth2.core.TestOAuth2RefreshTokens.refreshToken; @@ -186,7 +186,7 @@ public class OAuth2AuthorizationCodeGrantFilterTests { FilterChain filterChain = mock(FilterChain.class); MockHttpServletRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); this.filter.doFilter(authorizationResponse, response, filterChain); - verifyNoInteractions(filterChain); + verifyZeroInteractions(filterChain); // 2) redirect_uri with query parameters AND authorization response additional parameters Map additionalParameters = new LinkedHashMap<>(); @@ -196,7 +196,7 @@ public class OAuth2AuthorizationCodeGrantFilterTests { this.setUpAuthorizationRequest(authorizationRequest, response, this.registration1); authorizationResponse = createAuthorizationResponse(authorizationRequest, additionalParameters); this.filter.doFilter(authorizationResponse, response, filterChain); - verifyNoInteractions(filterChain); + verifyZeroInteractions(filterChain); } // gh-7963 From 5ce0ce3f3888ec7a6eab730e59a8c9a3f48c1d08 Mon Sep 17 00:00:00 2001 From: Rafael Renan Pacheco Date: Thu, 26 Dec 2019 16:44:03 -0300 Subject: [PATCH 046/348] Fix var typo and code readability --- .../_includes/servlet/oauth2/oauth2-resourceserver.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc index 4a58d084ae..bee1e52d99 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc @@ -456,9 +456,11 @@ public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter { Converter grantedAuthoritiesExtractor() { JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter (new GrantedAuthoritiesExtractor()); - return jwtAuthenticationConveter; + + return jwtAuthenticationConverter; } ---- From 8acdb82e6a1f6e67fdde6b0276054140e524d641 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 10 Feb 2020 06:57:31 -0500 Subject: [PATCH 047/348] OAuth2AuthorizationCodeGrantWebFilter matches on query parameters Fixes gh-7966 --- ...OAuth2AuthorizationCodeGrantWebFilter.java | 59 ++++-- ...ationCodeAuthenticationTokenConverter.java | 9 +- ...2AuthorizationCodeGrantWebFilterTests.java | 197 ++++++++++++++---- 3 files changed, 201 insertions(+), 64 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java index de6e31d458..7ef55667cb 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -37,13 +37,20 @@ import org.springframework.security.web.server.authentication.ServerAuthenticati import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; +import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; +import java.net.URI; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + /** * A {@code Filter} for the OAuth 2.0 Authorization Code Grant, * which handles the processing of the OAuth 2.0 Authorization Response. @@ -165,10 +172,10 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { return this.requiresAuthenticationMatcher.matches(exchange) - .filter( matchResult -> matchResult.isMatch()) - .flatMap( matchResult -> this.authenticationConverter.convert(exchange)) + .filter(ServerWebExchangeMatcher.MatchResult::isMatch) + .flatMap(matchResult -> this.authenticationConverter.convert(exchange)) .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) - .flatMap( token -> authenticate(exchange, chain, token)); + .flatMap(token -> authenticate(exchange, chain, token)); } private Mono authenticate(ServerWebExchange exchange, @@ -198,20 +205,34 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter { } private Mono matchesAuthorizationResponse(ServerWebExchange exchange) { - return this.authorizationRequestRepository.loadAuthorizationRequest(exchange) - .flatMap(authorizationRequest -> { - String requestUrl = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()) - .query(null) - .build() - .toUriString(); - MultiValueMap queryParams = exchange.getRequest().getQueryParams(); - if (requestUrl.equals(authorizationRequest.getRedirectUri()) && - OAuth2AuthorizationResponseUtils.isAuthorizationResponse(queryParams)) { - return ServerWebExchangeMatcher.MatchResult.match(); - } - return ServerWebExchangeMatcher.MatchResult.notMatch(); - }) - .filter(ServerWebExchangeMatcher.MatchResult::isMatch) + return Mono.just(exchange) + .filter(exch -> OAuth2AuthorizationResponseUtils.isAuthorizationResponse(exch.getRequest().getQueryParams())) + .flatMap(exch -> this.authorizationRequestRepository.loadAuthorizationRequest(exchange) + .flatMap(authorizationRequest -> + matchesRedirectUri(exch.getRequest().getURI(), authorizationRequest.getRedirectUri()))) .switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch()); } + + private static Mono matchesRedirectUri( + URI authorizationResponseUri, String authorizationRequestRedirectUri) { + UriComponents requestUri = UriComponentsBuilder.fromUri(authorizationResponseUri).build(); + UriComponents redirectUri = UriComponentsBuilder.fromUriString(authorizationRequestRedirectUri).build(); + Set>> requestUriParameters = + new LinkedHashSet<>(requestUri.getQueryParams().entrySet()); + Set>> redirectUriParameters = + new LinkedHashSet<>(redirectUri.getQueryParams().entrySet()); + // Remove the additional request parameters (if any) from the authorization response (request) + // before doing an exact comparison with the authorizationRequest.getRedirectUri() parameters (if any) + requestUriParameters.retainAll(redirectUriParameters); + + if (Objects.equals(requestUri.getScheme(), redirectUri.getScheme()) && + Objects.equals(requestUri.getUserInfo(), redirectUri.getUserInfo()) && + Objects.equals(requestUri.getHost(), redirectUri.getHost()) && + Objects.equals(requestUri.getPort(), redirectUri.getPort()) && + Objects.equals(requestUri.getPath(), redirectUri.getPath()) && + Objects.equals(requestUriParameters.toString(), redirectUriParameters.toString())) { + return ServerWebExchangeMatcher.MatchResult.match(); + } + return ServerWebExchangeMatcher.MatchResult.notMatch(); + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java index 38a453f009..2e9bd68c17 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -28,7 +28,6 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; @@ -103,14 +102,10 @@ public class ServerOAuth2AuthorizationCodeAuthenticationTokenConverter } private static OAuth2AuthorizationResponse convertResponse(ServerWebExchange exchange) { - MultiValueMap queryParams = exchange.getRequest() - .getQueryParams(); String redirectUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI()) - .query(null) .build() .toUriString(); - return OAuth2AuthorizationResponseUtils - .convert(queryParams, redirectUri); + .convert(exchange.getRequest().getQueryParams(), redirectUri); } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java index f86256cbe0..db8f1b80c1 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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,25 +25,28 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; +import org.springframework.util.CollectionUtils; import org.springframework.web.server.handler.DefaultWebFilterChain; import reactor.core.publisher.Mono; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +import static org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests.request; /** * @author Rob Winch @@ -102,7 +105,7 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests { MockServerWebExchange exchange = MockServerWebExchange .from(MockServerHttpRequest.get("/")); DefaultWebFilterChain chain = new DefaultWebFilterChain( - e -> e.getResponse().setComplete()); + e -> e.getResponse().setComplete(), Collections.emptyList()); this.filter.filter(exchange, chain).block(); @@ -111,43 +114,161 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests { @Test public void filterWhenMatchThenAuthorizedClientSaved() { - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .redirectUri("/authorize/registration-id") - .build(); - OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success() - .redirectUri("/authorize/registration-id") - .build(); - OAuth2AuthorizationExchange authorizationExchange = - new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse); - ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); - Mono authentication = Mono.just( - new OAuth2AuthorizationCodeAuthenticationToken(registration, authorizationExchange)); - OAuth2AuthorizationCodeAuthenticationToken authenticated = TestOAuth2AuthorizationCodeAuthenticationTokens - .authenticated(); + ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); + when(this.clientRegistrationRepository.findByRegistrationId(any())) + .thenReturn(Mono.just(clientRegistration)); - when(this.authenticationManager.authenticate(any())).thenReturn( - Mono.just(authenticated)); + MockServerHttpRequest authorizationRequest = + createAuthorizationRequest("/authorization/callback"); + OAuth2AuthorizationRequest oauth2AuthorizationRequest = + createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration); when(this.authorizationRequestRepository.loadAuthorizationRequest(any())) - .thenReturn(Mono.just(authorizationRequest)); + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + when(this.authorizationRequestRepository.removeAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any())) .thenReturn(Mono.empty()); - ServerAuthenticationConverter converter = e -> authentication; + when(this.authenticationManager.authenticate(any())) + .thenReturn(Mono.just(TestOAuth2AuthorizationCodeAuthenticationTokens.authenticated())); - this.filter = new OAuth2AuthorizationCodeGrantWebFilter( - this.authenticationManager, converter, this.authorizedClientRepository); - this.filter.setAuthorizationRequestRepository(this.authorizationRequestRepository); - - MockServerHttpRequest request = MockServerHttpRequest - .get("/authorize/registration-id") - .queryParam(OAuth2ParameterNames.CODE, "code") - .queryParam(OAuth2ParameterNames.STATE, "state") - .build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); + MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse); DefaultWebFilterChain chain = new DefaultWebFilterChain( - e -> e.getResponse().setComplete()); + e -> e.getResponse().setComplete(), Collections.emptyList()); this.filter.filter(exchange, chain).block(); verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(AnonymousAuthenticationToken.class), any()); } + + // gh-7966 + @Test + public void filterWhenAuthorizationRequestRedirectUriParametersMatchThenProcessed() { + ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); + when(this.clientRegistrationRepository.findByRegistrationId(any())) + .thenReturn(Mono.just(clientRegistration)); + when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any())) + .thenReturn(Mono.empty()); + when(this.authenticationManager.authenticate(any())) + .thenReturn(Mono.just(TestOAuth2AuthorizationCodeAuthenticationTokens.authenticated())); + + // 1) redirect_uri with query parameters + Map parameters = new LinkedHashMap<>(); + parameters.put("param1", "value1"); + parameters.put("param2", "value2"); + MockServerHttpRequest authorizationRequest = + createAuthorizationRequest("/authorization/callback", parameters); + OAuth2AuthorizationRequest oauth2AuthorizationRequest = + createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration); + when(this.authorizationRequestRepository.loadAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + when(this.authorizationRequestRepository.removeAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + + MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse); + DefaultWebFilterChain chain = new DefaultWebFilterChain( + e -> e.getResponse().setComplete(), Collections.emptyList()); + + this.filter.filter(exchange, chain).block(); + verify(this.authenticationManager, times(1)).authenticate(any()); + + // 2) redirect_uri with query parameters AND authorization response additional parameters + Map additionalParameters = new LinkedHashMap<>(); + additionalParameters.put("auth-param1", "value1"); + additionalParameters.put("auth-param2", "value2"); + authorizationResponse = createAuthorizationResponse(authorizationRequest, additionalParameters); + exchange = MockServerWebExchange.from(authorizationResponse); + + this.filter.filter(exchange, chain).block(); + verify(this.authenticationManager, times(2)).authenticate(any()); + } + + // gh-7966 + @Test + public void filterWhenAuthorizationRequestRedirectUriParametersNotMatchThenNotProcessed() { + String requestUri = "/authorization/callback"; + Map parameters = new LinkedHashMap<>(); + parameters.put("param1", "value1"); + parameters.put("param2", "value2"); + MockServerHttpRequest authorizationRequest = + createAuthorizationRequest(requestUri, parameters); + ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); + OAuth2AuthorizationRequest oauth2AuthorizationRequest = + createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration); + when(this.authorizationRequestRepository.loadAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + + // 1) Parameter value + Map parametersNotMatch = new LinkedHashMap<>(parameters); + parametersNotMatch.put("param2", "value8"); + MockServerHttpRequest authorizationResponse = createAuthorizationResponse( + createAuthorizationRequest(requestUri, parametersNotMatch)); + MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse); + DefaultWebFilterChain chain = new DefaultWebFilterChain( + e -> e.getResponse().setComplete(), Collections.emptyList()); + + this.filter.filter(exchange, chain).block(); + verifyZeroInteractions(this.authenticationManager); + + // 2) Parameter order + parametersNotMatch = new LinkedHashMap<>(); + parametersNotMatch.put("param2", "value2"); + parametersNotMatch.put("param1", "value1"); + authorizationResponse = createAuthorizationResponse( + createAuthorizationRequest(requestUri, parametersNotMatch)); + exchange = MockServerWebExchange.from(authorizationResponse); + + this.filter.filter(exchange, chain).block(); + verifyZeroInteractions(this.authenticationManager); + + // 3) Parameter missing + parametersNotMatch = new LinkedHashMap<>(parameters); + parametersNotMatch.remove("param2"); + authorizationResponse = createAuthorizationResponse( + createAuthorizationRequest(requestUri, parametersNotMatch)); + exchange = MockServerWebExchange.from(authorizationResponse); + + this.filter.filter(exchange, chain).block(); + verifyZeroInteractions(this.authenticationManager); + } + + private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest( + MockServerHttpRequest authorizationRequest, ClientRegistration registration) { + Map attributes = new HashMap<>(); + attributes.put(OAuth2ParameterNames.REGISTRATION_ID, registration.getRegistrationId()); + return request() + .attributes(attributes) + .redirectUri(authorizationRequest.getURI().toString()) + .build(); + } + + private static MockServerHttpRequest createAuthorizationRequest(String requestUri) { + return createAuthorizationRequest(requestUri, new LinkedHashMap<>()); + } + + private static MockServerHttpRequest createAuthorizationRequest(String requestUri, Map parameters) { + MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest + .get(requestUri); + if (!CollectionUtils.isEmpty(parameters)) { + parameters.forEach(builder::queryParam); + } + return builder.build(); + } + + private static MockServerHttpRequest createAuthorizationResponse(MockServerHttpRequest authorizationRequest) { + return createAuthorizationResponse(authorizationRequest, new LinkedHashMap<>()); + } + + private static MockServerHttpRequest createAuthorizationResponse( + MockServerHttpRequest authorizationRequest, Map additionalParameters) { + MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest + .get(authorizationRequest.getURI().toString()); + builder.queryParam(OAuth2ParameterNames.CODE, "code"); + builder.queryParam(OAuth2ParameterNames.STATE, "state"); + additionalParameters.forEach(builder::queryParam); + builder.cookies(authorizationRequest.getCookies()); + return builder.build(); + } } From 3dbfef9ef1e7cdb6892c6c413b6a40a6ae7b7727 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 24 Feb 2020 14:49:21 -0500 Subject: [PATCH 048/348] OAuth2AccessTokenResponseHttpMessageConverter handles JSON object parameters Fixes gh-6463 --- ...cessTokenResponseHttpMessageConverter.java | 17 ++++++--- ...okenResponseHttpMessageConverterTests.java | 35 ++++++++++++++++++- 2 files changed, 46 insertions(+), 6 deletions(-) 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..20c688d0b3 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. @@ -43,6 +43,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.HashMap; +import java.util.stream.Collectors; /** * A {@link HttpMessageConverter} for an {@link OAuth2AccessTokenResponse OAuth 2.0 Access Token Response}. @@ -55,8 +56,8 @@ import java.util.HashMap; public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpMessageConverter { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE = - new ParameterizedTypeReference>() {}; + private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE = + new ParameterizedTypeReference>() {}; private GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); @@ -80,10 +81,16 @@ public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpM throws HttpMessageNotReadableException { try { + // gh-6463 + // Parse parameter values as Object in order to handle potential JSON Object and then convert values to String @SuppressWarnings("unchecked") - Map tokenResponseParameters = (Map) this.jsonMessageConverter.read( + Map tokenResponseParameters = (Map) this.jsonMessageConverter.read( PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage); - return this.tokenResponseConverter.convert(tokenResponseParameters); + return this.tokenResponseConverter.convert( + tokenResponseParameters.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().toString()))); } catch (Exception ex) { throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex, inputMessage); diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java index bf74c94730..dace9010c1 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.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. @@ -96,6 +96,39 @@ public class OAuth2AccessTokenResponseHttpMessageConverterTests { } + // gh-6463 + @Test + public void readInternalWhenSuccessfulTokenResponseWithObjectThenReadOAuth2AccessTokenResponse() { + String tokenResponse = "{\n" + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": 3600,\n" + + " \"scope\": \"read write\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_object_1\": {\"name1\": \"value1\"},\n" + + " \"custom_object_2\": [\"value1\", \"value2\"],\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"; + + MockClientHttpResponse response = new MockClientHttpResponse( + tokenResponse.getBytes(), HttpStatus.OK); + + OAuth2AccessTokenResponse accessTokenResponse = this.messageConverter.readInternal( + OAuth2AccessTokenResponse.class, response); + + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBeforeOrEqualTo(Instant.now().plusSeconds(3600)); + assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write"); + assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); + assertThat(accessTokenResponse.getAdditionalParameters()).containsExactly( + entry("custom_object_1", "{name1=value1}"), + entry("custom_object_2", "[value1, value2]"), + entry("custom_parameter_1", "custom-value-1"), + entry("custom_parameter_2", "custom-value-2")); + } + @Test public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() { Converter tokenResponseConverter = mock(Converter.class); From 9092115b8acccaef96736ce715df1271632e27f4 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 28 Feb 2020 15:29:21 -0700 Subject: [PATCH 049/348] Register Authentication Provider in Init Phase Fixes gh-8031 --- .../OAuth2ResourceServerConfigurer.java | 48 ++++++++++++++----- .../OAuth2ResourceServerConfigurerTests.java | 31 +++++++++++- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 367465b713..4e2ef7062a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -222,9 +222,16 @@ public final class OAuth2ResourceServerConfigurer this.spring.register(MultipleBearerTokenResolverBeansConfig.class).autowire()) + assertThatCode(() -> this.spring + .register(MultipleBearerTokenResolverBeansConfig.class, JwtDecoderConfig.class).autowire()) .isInstanceOf(BeanCreationException.class) .hasRootCauseInstanceOf(NoUniqueBeanDefinitionException.class); } @@ -1469,6 +1482,22 @@ public class OAuth2ResourceServerConfigurerTests { } } + @EnableWebSecurity + static class AnonymousDisabledConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .anonymous().disable() + .oauth2ResourceServer() + .jwt(); + // @formatter:on + } + } + @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) static class MethodSecurityConfig extends WebSecurityConfigurerAdapter { From 96ff3a54a97b11031da2b0f3033361a32fe082a5 Mon Sep 17 00:00:00 2001 From: AmitB Date: Sat, 29 Feb 2020 20:57:19 +0530 Subject: [PATCH 050/348] Fix typo in AntPathRequestMatcher contructor comment --- .../security/web/util/matcher/AntPathRequestMatcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java index b77be2390b..1acaac55cc 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java @@ -108,7 +108,7 @@ public final class AntPathRequestMatcher * * @param pattern the ant pattern to use for matching * @param httpMethod the HTTP method. The {@code matches} method will return false if - * the incoming request doesn't doesn't have the same method. + * the incoming request doesn't have the same method. * @param caseSensitive true if the matcher should consider case, else false * @param urlPathHelper if non-null, will be used for extracting the path from the HttpServletRequest */ From 32c02fbedb8ac2c79a1df3b05884077ee329e243 Mon Sep 17 00:00:00 2001 From: Clement Stoquart Date: Tue, 26 Nov 2019 09:20:27 +0100 Subject: [PATCH 051/348] Remove empty relay state from redirect url --- ...aml2WebSsoAuthenticationRequestFilter.java | 13 +- ...ebSsoAuthenticationRequestFilterTests.java | 91 ++++++++++++++ .../filter/TestSaml2SigningCredentials.java | 118 ++++++++++++++++++ 3 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/TestSaml2SigningCredentials.java diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java index 7bc25a0994..b27927647f 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilter.java @@ -25,6 +25,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher.MatchResult; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; @@ -94,13 +95,17 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter String xml = this.authenticationRequestFactory.createAuthenticationRequest(authNRequest); String encoded = encode(deflate(xml)); String relayState = request.getParameter("RelayState"); - String redirect = UriComponentsBuilder + UriComponentsBuilder uriBuilder = UriComponentsBuilder .fromUriString(relyingParty.getIdpWebSsoUrl()) - .queryParam("SAMLRequest", UriUtils.encode(encoded, StandardCharsets.ISO_8859_1)) - .queryParam("RelayState", UriUtils.encode(relayState, StandardCharsets.ISO_8859_1)) + .queryParam("SAMLRequest", UriUtils.encode(encoded, StandardCharsets.ISO_8859_1)); + + if (StringUtils.hasText(relayState)) { + uriBuilder.queryParam("RelayState", UriUtils.encode(relayState, StandardCharsets.ISO_8859_1)); + } + + return uriBuilder .build(true) .toUriString(); - return redirect; } private Saml2AuthenticationRequest createAuthenticationRequest(RelyingPartyRegistration relyingParty, HttpServletRequest request) { diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java new file mode 100644 index 0000000000..c17e9c820c --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationRequestFilterTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2019 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.saml2.provider.service.servlet.filter; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.security.saml2.provider.service.servlet.filter.TestSaml2SigningCredentials.signingCredential; + +public class Saml2WebSsoAuthenticationRequestFilterTests { + + private Saml2WebSsoAuthenticationRequestFilter filter; + private RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class); + private MockHttpServletRequest request; + private HttpServletResponse response; + private MockFilterChain filterChain; + + @Before + public void setup() { + filter = new Saml2WebSsoAuthenticationRequestFilter(repository); + request = new MockHttpServletRequest(); + response = new MockHttpServletResponse(); + request.setPathInfo("/saml2/authenticate/registration-id"); + + filterChain = new MockFilterChain(); + } + + @Test + public void createSamlRequestRedirectUrlAndReturnUrlWithoutRelayState() throws ServletException, IOException { + RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration + .withRegistrationId("registration-id") + .remoteIdpEntityId("idp-entity-id") + .idpWebSsoUrl("sso-url") + .assertionConsumerServiceUrlTemplate("template") + .credentials(c -> c.add(signingCredential())) + .build(); + + when(repository.findByRegistrationId("registration-id")) + .thenReturn(relyingPartyRegistration); + + filter.doFilterInternal(request, response, filterChain); + + Assert.assertFalse(response.getHeader("Location").contains("RelayState=")); + } + + @Test + public void createSamlRequestRedirectUrlAndReturnUrlWithRelayState() throws ServletException, IOException { + RelyingPartyRegistration relyingPartyRegistration = RelyingPartyRegistration + .withRegistrationId("registration-id") + .remoteIdpEntityId("idp-entity-id") + .idpWebSsoUrl("sso-url") + .assertionConsumerServiceUrlTemplate("template") + .credentials(c -> c.add(signingCredential())) + .build(); + + when(repository.findByRegistrationId("registration-id")) + .thenReturn(relyingPartyRegistration); + + request.setParameter("RelayState", "my-relay-state"); + + filter.doFilterInternal(request, response, filterChain); + + Assert.assertTrue(response.getHeader("Location").contains("RelayState=my-relay-state")); + } +} diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/TestSaml2SigningCredentials.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/TestSaml2SigningCredentials.java new file mode 100644 index 0000000000..3aa718227e --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/TestSaml2SigningCredentials.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2019 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.saml2.provider.service.servlet.filter; + +import java.io.ByteArrayInputStream; +import java.security.KeyException; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + +import org.opensaml.security.crypto.KeySupport; +import org.springframework.security.saml2.Saml2Exception; +import org.springframework.security.saml2.credentials.Saml2X509Credential; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.DECRYPTION; +import static org.springframework.security.saml2.credentials.Saml2X509Credential.Saml2X509CredentialType.SIGNING; + +final class TestSaml2SigningCredentials { + + static Saml2X509Credential signingCredential() { + return new Saml2X509Credential(idpPrivateKey(), idpCertificate(), SIGNING, DECRYPTION); + } + + private static X509Certificate certificate(String cert) { + ByteArrayInputStream certBytes = new ByteArrayInputStream(cert.getBytes()); + try { + return (X509Certificate) CertificateFactory + .getInstance("X.509") + .generateCertificate(certBytes); + } + catch (CertificateException e) { + throw new Saml2Exception(e); + } + } + + private static PrivateKey privateKey(String key) { + try { + return KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]); + } + catch (KeyException e) { + throw new Saml2Exception(e); + } + } + + private static X509Certificate idpCertificate() { + return certificate("-----BEGIN CERTIFICATE-----\n" + + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n" + + "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n" + + "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n" + + "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n" + + "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n" + + "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n" + + "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n" + + "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n" + + "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n" + + "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n" + + "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n" + + "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n" + + "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n" + + "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n" + + "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n" + + "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n" + + "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n" + + "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n" + + "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n" + + "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n" + + "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n" + + "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + + "-----END CERTIFICATE-----\n"); + } + + private static PrivateKey idpPrivateKey() { + return privateKey("-----BEGIN PRIVATE KEY-----\n" + + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC4cn62E1xLqpN3\n" + + "4PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZX\n" + + "W+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHE\n" + + "fDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7h\n" + + "Z6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/T\n" + + "Xy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7\n" + + "I+J5lS8VAgMBAAECggEBAKyxBlIS7mcp3chvq0RF7B3PHFJMMzkwE+t3pLJcs4cZ\n" + + "nezh/KbREfP70QjXzk/llnZCvxeIs5vRu24vbdBm79qLHqBuHp8XfHHtuo2AfoAQ\n" + + "l4h047Xc/+TKMivnPQ0jX9qqndKDLqZDf5wnbslDmlskvF0a/MjsLU0TxtOfo+dB\n" + + "t55FW11cGqxZwhS5Gnr+cbw3OkHz23b9gEOt9qfwPVepeysbmm9FjU+k4yVa7rAN\n" + + "xcbzVb6Y7GCITe2tgvvEHmjB9BLmWrH3mZ3Af17YU/iN6TrpPd6Sj3QoS+2wGtAe\n" + + "HbUs3CKJu7bIHcj4poal6Kh8519S+erJTtqQ8M0ZiEECgYEA43hLYAPaUueFkdfh\n" + + "9K/7ClH6436CUH3VdizwUXi26fdhhV/I/ot6zLfU2mgEHU22LBECWQGtAFm8kv0P\n" + + "zPn+qjaR3e62l5PIlSYbnkIidzoDZ2ztu4jF5LgStlTJQPteFEGgZVl5o9DaSZOq\n" + + "Yd7G3XqXuQ1VGMW58G5FYJPtA1cCgYEAz5TPUtK+R2KXHMjUwlGY9AefQYRYmyX2\n" + + "Tn/OFgKvY8lpAkMrhPKONq7SMYc8E9v9G7A0dIOXvW7QOYSapNhKU+np3lUafR5F\n" + + "4ZN0bxZ9qjHbn3AMYeraKjeutHvlLtbHdIc1j3sxe/EzltRsYmiqLdEBW0p6hwWg\n" + + "tyGhYWVyaXMCgYAfDOKtHpmEy5nOCLwNXKBWDk7DExfSyPqEgSnk1SeS1HP5ctPK\n" + + "+1st6sIhdiVpopwFc+TwJWxqKdW18tlfT5jVv1E2DEnccw3kXilS9xAhWkfwrEvf\n" + + "V5I74GydewFl32o+NZ8hdo9GL1I8zO1rIq/et8dSOWGuWf9BtKu/vTGTTQKBgFxU\n" + + "VjsCnbvmsEwPUAL2hE/WrBFaKocnxXx5AFNt8lEyHtDwy4Sg1nygGcIJ4sD6koQk\n" + + "RdClT3LkvR04TAiSY80bN/i6ZcPNGUwSaDGZEWAIOSWbkwZijZNFnSGOEgxZX/IG\n" + + "yd39766vREEMTwEeiMNEOZQ/dmxkJm4OOVe25cLdAoGACOtPnq1Fxay80UYBf4rQ\n" + + "+bJ9yX1ulB8WIree1hD7OHSB2lRHxrVYWrglrTvkh63Lgx+EcsTV788OsvAVfPPz\n" + + "BZrn8SdDlQqalMxUBYEFwnsYD3cQ8yOUnijFVC4xNcdDv8OIqVgSk4KKxU5AshaA\n" + + "xk6Mox+u8Cc2eAK12H13i+8=\n" + + "-----END PRIVATE KEY-----\n"); + } +} From 8fa16ce63e3ca8bfedbb6e19af89498704aa005d Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 9 Mar 2020 10:03:18 -0600 Subject: [PATCH 052/348] Update to Jetty 9.4.27 Fixes gh-7507 --- gradle/dependency-management.gradle | 22 +++++++++---------- .../samples/cas/JettyCasService.groovy | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 5d2330178f..4dbec16b08 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -162,17 +162,17 @@ dependencyManagement { dependency 'org.codehaus.groovy:groovy-json:2.4.17' dependency 'org.codehaus.groovy:groovy:2.4.17' dependency 'org.eclipse.jdt:ecj:3.12.3' - dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.19.v20190610' - dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.19.v20190610' - dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-client:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-http:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-io:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-security:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-server:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-servlet:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-util:9.4.19.v20190610' - dependency 'org.eclipse.jetty:jetty-xml:9.4.19.v20190610' + dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.27.v20200227' + dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227' + dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-client:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-http:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-io:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-security:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-server:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-servlet:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-util:9.4.27.v20200227' + dependency 'org.eclipse.jetty:jetty-xml:9.4.27.v20200227' dependency 'org.eclipse.persistence:javax.persistence:2.2.1' dependency 'org.gebish:geb-ast:0.10.0' dependency 'org.gebish:geb-core:0.10.0' diff --git a/samples/xml/cas/cassample/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy b/samples/xml/cas/cassample/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy index b5b8f2feb3..8be5860fb2 100644 --- a/samples/xml/cas/cassample/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy +++ b/samples/xml/cas/cassample/src/integration-test/groovy/org/springframework/security/samples/cas/JettyCasService.groovy @@ -66,7 +66,7 @@ class JettyCasService extends Server { String password = System.getProperty('javax.net.ssl.trustStorePassword','password') - SslContextFactory sslContextFactory = new SslContextFactory(); + SslContextFactory sslContextFactory = new SslContextFactory.Server(); sslContextFactory.setKeyStorePath(getTrustStore()); sslContextFactory.setKeyStorePassword(password); sslContextFactory.setKeyManagerPassword(password); From 75f22285c687a8e8c99857484d1612493109fa6e Mon Sep 17 00:00:00 2001 From: Markus Engelbrecht Date: Wed, 11 Mar 2020 05:27:18 +0100 Subject: [PATCH 053/348] Fix typo 'properites' in documentation Fixes gh-8095 --- .../web/authentication/ServiceAuthenticationDetailsSource.java | 2 +- .../web/configurers/AbstractAuthenticationFilterConfigurer.java | 2 +- .../authentication/jaas/JaasAuthenticationProvider.java | 2 +- .../context/support/WithMockUserSecurityContextFactory.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetailsSource.java b/cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetailsSource.java index 63613d8fe6..c323bb491b 100644 --- a/cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetailsSource.java +++ b/cas/src/main/java/org/springframework/security/cas/web/authentication/ServiceAuthenticationDetailsSource.java @@ -47,7 +47,7 @@ public class ServiceAuthenticationDetailsSource implements // =================================================================================================== /** - * Creates an implementation that uses the specified ServiceProperites and the default + * Creates an implementation that uses the specified ServiceProperties and the default * CAS artifactParameterName. * * @param serviceProperties The ServiceProperties to use to construct the serviceUrl. diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java index 4ae0ffc162..0dfd232009 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java @@ -172,7 +172,7 @@ public abstract class AbstractAuthenticationFilterConfigurer * - * A configuration note: The JaasAuthenticationProvider uses the security properites + * A configuration note: The JaasAuthenticationProvider uses the security properties * "login.config.url.X" to configure jaas. If you would like to customize the way Jaas * gets configured, create a subclass of this and override the * {@link #configureJaas(Resource)} method. diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithMockUserSecurityContextFactory.java b/test/src/main/java/org/springframework/security/test/context/support/WithMockUserSecurityContextFactory.java index 8c11be54db..d2cc86d966 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithMockUserSecurityContextFactory.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithMockUserSecurityContextFactory.java @@ -43,7 +43,7 @@ final class WithMockUserSecurityContextFactory implements .username() : withUser.value(); if (username == null) { throw new IllegalArgumentException(withUser - + " cannot have null username on both username and value properites"); + + " cannot have null username on both username and value properties"); } List grantedAuthorities = new ArrayList<>(); From a49a325db27e4aa8b1b3472db6d74772d59da20d Mon Sep 17 00:00:00 2001 From: Zeeshan Adnan Date: Fri, 13 Mar 2020 02:32:19 +0600 Subject: [PATCH 054/348] Fix exception for empty basic auth header token fixes spring-projectsgh-7976 --- .../www/BasicAuthenticationConverter.java | 4 ++++ .../www/BasicAuthenticationConverterTests.java | 8 ++++++++ .../www/BasicAuthenticationFilterTests.java | 16 ++++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java index abd192b263..3bac82ef10 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverter.java @@ -87,6 +87,10 @@ public class BasicAuthenticationConverter implements AuthenticationConverter { return null; } + if (header.equalsIgnoreCase(AUTHENTICATION_SCHEME_BASIC)) { + throw new BadCredentialsException("Empty basic authentication token"); + } + byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8); byte[] decoded; try { diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java index e24499036b..1541363429 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationConverterTests.java @@ -111,4 +111,12 @@ public class BasicAuthenticationConverterTests { assertThat(authentication.getName()).isEqualTo("rod"); assertThat(authentication.getCredentials()).isEqualTo(""); } + + @Test(expected = BadCredentialsException.class) + public void requestWhenEmptyBasicAuthorizationHeaderTokenThenError() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Basic "); + converter.convert(request); + } + } diff --git a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java index c03b132807..533b25444d 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilterTests.java @@ -424,4 +424,20 @@ public class BasicAuthenticationFilterTests { assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); } + @Test + public void requestWhenEmptyBasicAuthorizationHeaderTokenThenUnauthorized() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Basic "); + request.setServletPath("/some_file.html"); + request.setSession(new MockHttpSession()); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + FilterChain chain = mock(FilterChain.class); + filter.doFilter(request, response, chain); + verify(chain, never()).doFilter(any(ServletRequest.class), + any(ServletResponse.class)); + assertThat(SecurityContextHolder.getContext().getAuthentication()).isNull(); + assertThat(response.getStatus()).isEqualTo(401); + } + } From 86e25ff2aba51e0a2e53eefdc05b5e6e7cba7e14 Mon Sep 17 00:00:00 2001 From: Erik van Paassen Date: Tue, 17 Mar 2020 17:30:48 +0100 Subject: [PATCH 055/348] Fix typo in Javadoc of HttpSecurity#csrf() `HttpSecurity#csrf()` obviously returns a `CsrfConfigurer`, while the Javadoc states that it returns the `ServletApiConfigurer`. --- .../security/config/annotation/web/builders/HttpSecurity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index fdf28d8f7d..aab7f31d09 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -1471,7 +1471,7 @@ public final class HttpSecurity extends * } * * - * @return the {@link ServletApiConfigurer} for further customizations + * @return the {@link CsrfConfigurer} for further customizations * @throws Exception */ public CsrfConfigurer csrf() throws Exception { From 256aba7b378b6f5c7a3bfd3f5c19e7ccc38b8798 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 19 Mar 2020 17:19:27 -0400 Subject: [PATCH 056/348] Fix rsocket test Request route that exists; add additional error message verification Fixes gh-8154 --- .../rsocket/RSocketMessageHandlerITests.java | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java index 6af06326bf..19f5e1010c 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java @@ -103,7 +103,9 @@ public class RSocketMessageHandlerITests { .data(data) .retrieveMono(String.class) .block() - ).isInstanceOf(ApplicationErrorException.class); + ).isInstanceOf(ApplicationErrorException.class) + .hasMessageContaining("Access Denied"); + assertThat(this.controller.payloads).isEmpty(); } @@ -116,7 +118,9 @@ public class RSocketMessageHandlerITests { .data(data) .retrieveMono(String.class) .block() - ).isInstanceOf(ApplicationErrorException.class); + ).isInstanceOf(ApplicationErrorException.class) + .hasMessageContaining("Invalid Credentials"); + assertThat(this.controller.payloads).isEmpty(); } @@ -149,12 +153,13 @@ public class RSocketMessageHandlerITests { @Test public void retrieveFluxWhenDataFluxAndSecureThenDenied() throws Exception { Flux data = Flux.just("a", "b", "c"); - assertThatCode(() -> this.requester.route("secure.secure.retrieve-flux") + assertThatCode(() -> this.requester.route("secure.retrieve-flux") .data(data, String.class) .retrieveFlux(String.class) .collectList() - .block()).isInstanceOf( - ApplicationErrorException.class); + .block() + ).isInstanceOf(ApplicationErrorException.class) + .hasMessageContaining("Access Denied"); assertThat(this.controller.payloads).isEmpty(); } @@ -179,8 +184,9 @@ public class RSocketMessageHandlerITests { .data(data) .retrieveFlux(String.class) .collectList() - .block()).isInstanceOf( - ApplicationErrorException.class); + .block() + ).isInstanceOf(ApplicationErrorException.class) + .hasMessageContaining("Access Denied"); assertThat(this.controller.payloads).isEmpty(); } From 512ad9e7e48c0db7183994aaaa674381b38ca819 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Thu, 19 Mar 2020 13:54:44 -0400 Subject: [PATCH 057/348] Document AuthorizedClientServiceOAuth2AuthorizedClientManager Fixes gh-8152 --- .../servlet/oauth2/oauth2-client.adoc | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc index 471b0fe826..c245c6a956 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-client.adoc @@ -343,6 +343,36 @@ private Function> contextAttributesM } ---- +The `DefaultOAuth2AuthorizedClientManager` is designed to be used *_within_* the context of a `HttpServletRequest`. +When operating *_outside_* of a `HttpServletRequest` context, use `AuthorizedClientServiceOAuth2AuthorizedClientManager` instead. + +A _service application_ is a common use case for when to use an `AuthorizedClientServiceOAuth2AuthorizedClientManager`. +Service applications often run in the background, without any user interaction, and typically run under a system-level account instead of a user account. +An OAuth 2.0 Client configured with the `client_credentials` grant type can be considered a type of service application. + +The following code shows an example of how to configure an `AuthorizedClientServiceOAuth2AuthorizedClientManager` that provides support for the `client_credentials` grant type: + +[source,java] +---- +@Bean +public OAuth2AuthorizedClientManager authorizedClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientService authorizedClientService) { + + OAuth2AuthorizedClientProvider authorizedClientProvider = + OAuth2AuthorizedClientProviderBuilder.builder() + .clientCredentials() + .build(); + + AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager = + new AuthorizedClientServiceOAuth2AuthorizedClientManager( + clientRegistrationRepository, authorizedClientService); + authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); + + return authorizedClientManager; +} +---- + [[oauth2Client-auth-grant-support]] === Authorization Grant Support From f06aa724bfad75c6febb54d22147deb0a530ce22 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 24 Mar 2020 14:43:49 -0400 Subject: [PATCH 058/348] OAuth2ErrorHttpMessageConverter handles JSON object parameters Fixes gh-8157 --- .../OAuth2ErrorHttpMessageConverter.java | 17 ++++++++++----- .../OAuth2ErrorHttpMessageConverterTests.java | 21 ++++++++++++++++++- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java index 1e60804d80..a9901c97d9 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverter.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. @@ -34,6 +34,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; /** * A {@link HttpMessageConverter} for an {@link OAuth2Error OAuth 2.0 Error}. @@ -46,8 +47,8 @@ import java.util.Map; public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverter { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE = - new ParameterizedTypeReference>() {}; + private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE = + new ParameterizedTypeReference>() {}; private GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); @@ -69,10 +70,16 @@ public class OAuth2ErrorHttpMessageConverter extends AbstractHttpMessageConverte throws HttpMessageNotReadableException { try { + // gh-8157 + // Parse parameter values as Object in order to handle potential JSON Object and then convert values to String @SuppressWarnings("unchecked") - Map errorParameters = (Map) this.jsonMessageConverter.read( + Map errorParameters = (Map) this.jsonMessageConverter.read( PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage); - return this.errorConverter.convert(errorParameters); + return this.errorConverter.convert( + errorParameters.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> String.valueOf(entry.getValue())))); } catch (Exception ex) { throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Error: " + ex.getMessage(), ex, inputMessage); diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java index a57b1df1b8..11211aad56 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2ErrorHttpMessageConverterTests.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. @@ -78,6 +78,25 @@ public class OAuth2ErrorHttpMessageConverterTests { assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2"); } + // gh-8157 + @Test + public void readInternalWhenErrorResponseWithObjectThenReadOAuth2Error() throws Exception { + String errorResponse = "{\n" + + " \"error\": \"unauthorized_client\",\n" + + " \"error_description\": \"The client is not authorized\",\n" + + " \"error_codes\": [65001],\n" + + " \"error_uri\": \"https://tools.ietf.org/html/rfc6749#section-5.2\"\n" + + "}\n"; + + MockClientHttpResponse response = new MockClientHttpResponse( + errorResponse.getBytes(), HttpStatus.BAD_REQUEST); + + OAuth2Error oauth2Error = this.messageConverter.readInternal(OAuth2Error.class, response); + assertThat(oauth2Error.getErrorCode()).isEqualTo("unauthorized_client"); + assertThat(oauth2Error.getDescription()).isEqualTo("The client is not authorized"); + assertThat(oauth2Error.getUri()).isEqualTo("https://tools.ietf.org/html/rfc6749#section-5.2"); + } + @Test public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() { Converter errorConverter = mock(Converter.class); From 98bd1a3f604d04a4de80ce5277345c9b1e5c20ab Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 24 Mar 2020 15:35:03 -0600 Subject: [PATCH 059/348] Polish Resource Server JWT Docs Issue gh-5935 --- .../_includes/servlet/oauth2/oauth2-resourceserver.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc index bee1e52d99..db7a546e2b 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc @@ -77,12 +77,12 @@ So long as this scheme is indicated, Resource Server will attempt to process the Given a well-formed JWT, Resource Server will: -1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWTs header -2. Validate the JWTs `exp` and `nbf` timestamps and the JWTs `iss` claim, and +1. Validate its signature against a public key obtained from the `jwks_url` endpoint during startup and matched against the JWT +2. Validate the JWT's `exp` and `nbf` timestamps and the JWT's `iss` claim, and 3. Map each scope to an authority with the prefix `SCOPE_`. [NOTE] -As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate the JWT tokens. +As the authorization server makes available new keys, Spring Security will automatically rotate the keys used to validate JWTs. The resulting `Authentication#getPrincipal`, by default, is a Spring Security `Jwt` object, and `Authentication#getName` maps to the JWT's `sub` property, if one is present. From 4706b16a2bd673c23c7f6c66ca65be529d7530f7 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 25 Mar 2020 20:32:52 -0400 Subject: [PATCH 060/348] oauth2Login WebFlux does not auto-redirect for XHR request Fixes gh-8118 --- .../config/web/server/ServerHttpSecurity.java | 60 ++++++++++++++----- .../config/web/server/OAuth2LoginTests.java | 19 +++++- 2 files changed, 64 insertions(+), 15 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 04fd40d84f..2b97f1aa5b 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -1181,24 +1181,56 @@ public class ServerHttpSecurity { authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler()); authenticationFilter.setSecurityContextRepository(this.securityContextRepository); - MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher( - MediaType.TEXT_HTML); - htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); - Map urlToText = http.oauth2Login.getLinks(); - String authenticationEntryPointRedirectPath; - if (urlToText.size() == 1) { - authenticationEntryPointRedirectPath = urlToText.keySet().iterator().next(); - } else { - authenticationEntryPointRedirectPath = "/login"; - } - RedirectServerAuthenticationEntryPoint entryPoint = new RedirectServerAuthenticationEntryPoint(authenticationEntryPointRedirectPath); - entryPoint.setRequestCache(http.requestCache.requestCache); - http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, entryPoint)); + setDefaultEntryPoints(http); http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC); http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION); } + private void setDefaultEntryPoints(ServerHttpSecurity http) { + String defaultLoginPage = "/login"; + Map urlToText = http.oauth2Login.getLinks(); + String providerLoginPage = null; + if (urlToText.size() == 1) { + providerLoginPage = urlToText.keySet().iterator().next(); + } + + MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher( + MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), + MediaType.TEXT_HTML, MediaType.TEXT_PLAIN); + htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); + + ServerWebExchangeMatcher xhrMatcher = exchange -> { + if (exchange.getRequest().getHeaders().getOrEmpty("X-Requested-With").contains("XMLHttpRequest")) { + return ServerWebExchangeMatcher.MatchResult.match(); + } + return ServerWebExchangeMatcher.MatchResult.notMatch(); + }; + ServerWebExchangeMatcher notXhrMatcher = new NegatedServerWebExchangeMatcher(xhrMatcher); + + ServerWebExchangeMatcher defaultEntryPointMatcher = new AndServerWebExchangeMatcher( + notXhrMatcher, htmlMatcher); + + if (providerLoginPage != null) { + ServerWebExchangeMatcher loginPageMatcher = new PathPatternParserServerWebExchangeMatcher(defaultLoginPage); + ServerWebExchangeMatcher faviconMatcher = new PathPatternParserServerWebExchangeMatcher("/favicon.ico"); + ServerWebExchangeMatcher defaultLoginPageMatcher = new AndServerWebExchangeMatcher( + new OrServerWebExchangeMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher); + + ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher( + notXhrMatcher, new NegatedServerWebExchangeMatcher(defaultLoginPageMatcher)); + RedirectServerAuthenticationEntryPoint entryPoint = + new RedirectServerAuthenticationEntryPoint(providerLoginPage); + entryPoint.setRequestCache(http.requestCache.requestCache); + http.defaultEntryPoints.add(new DelegateEntry(matcher, entryPoint)); + } + + RedirectServerAuthenticationEntryPoint defaultEntryPoint = + new RedirectServerAuthenticationEntryPoint(defaultLoginPage); + defaultEntryPoint.setRequestCache(http.requestCache.requestCache); + http.defaultEntryPoints.add(new DelegateEntry(defaultEntryPointMatcher, defaultEntryPoint)); + } + private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) { if (this.authenticationSuccessHandler == null) { RedirectServerAuthenticationSuccessHandler handler = new RedirectServerAuthenticationSuccessHandler(); diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index a783a8212a..c820748e57 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; @@ -185,6 +186,22 @@ public class OAuth2LoginTests { assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize"); } + // gh-8118 + @Test + public void defaultLoginPageWithSingleClientRegistrationAndXhrRequestThenDoesNotRedirectForAuthorization() { + this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, WebFluxConfig.class).autowire(); + + this.client.get() + .uri("/") + .header("X-Requested-With", "XMLHttpRequest") + .exchange() + .expectStatus().is3xxRedirection() + .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); + } + + @EnableWebFlux + static class WebFluxConfig { } + @EnableWebFluxSecurity static class OAuth2LoginWithSingleClientRegistrations { @Bean From cb7786bf97b7a552c47e4aceaf6b92400e6c2dcc Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 3 Mar 2020 15:39:10 -0700 Subject: [PATCH 061/348] Malformed Bearer Token Returns 401 for WebFlux Fixes gh-7668 --- .../config/web/server/ServerHttpSecurity.java | 28 +++++++++++++++++-- .../server/OAuth2ResourceServerSpecTests.java | 11 ++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 2b97f1aa5b..22f73ba314 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -55,6 +55,7 @@ import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.config.Customizer; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; @@ -105,6 +106,7 @@ import org.springframework.security.web.server.DelegatingServerAuthenticationEnt import org.springframework.security.web.server.MatcherSecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; +import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; @@ -1823,6 +1825,28 @@ public class ServerHttpSecurity { } } + private class BearerTokenAuthenticationWebFilter extends AuthenticationWebFilter { + private ServerAuthenticationFailureHandler authenticationFailureHandler; + + BearerTokenAuthenticationWebFilter(ReactiveAuthenticationManager authenticationManager) { + super(authenticationManager); + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain); + return super.filter(exchange, chain) + .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler + .onAuthenticationFailure(webFilterExchange, e)); + } + + @Override + public void setAuthenticationFailureHandler(ServerAuthenticationFailureHandler authenticationFailureHandler) { + super.setAuthenticationFailureHandler(authenticationFailureHandler); + this.authenticationFailureHandler = authenticationFailureHandler; + } + } + /** * Configures JWT Resource Server Support */ @@ -1896,7 +1920,7 @@ public class ServerHttpSecurity { protected void configure(ServerHttpSecurity http) { ReactiveAuthenticationManager authenticationManager = getAuthenticationManager(); - AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager); + AuthenticationWebFilter oauth2 = new BearerTokenAuthenticationWebFilter(authenticationManager); oauth2.setServerAuthenticationConverter(bearerTokenConverter); oauth2.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint)); http @@ -2002,7 +2026,7 @@ public class ServerHttpSecurity { protected void configure(ServerHttpSecurity http) { ReactiveAuthenticationManager authenticationManager = getAuthenticationManager(); - AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager); + AuthenticationWebFilter oauth2 = new BearerTokenAuthenticationWebFilter(authenticationManager); oauth2.setServerAuthenticationConverter(bearerTokenConverter); oauth2.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(entryPoint)); http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION); diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java index 8362ea105c..4bd3c17a0d 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java @@ -172,6 +172,17 @@ public class OAuth2ResourceServerSpecTests { .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); } + @Test + public void getWhenEmptyBearerTokenThenReturnsInvalidToken() { + this.spring.register(PublicKeyConfig.class).autowire(); + + this.client.get() + .headers(headers -> headers.add("Authorization", "Bearer ")) + .exchange() + .expectStatus().isUnauthorized() + .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); + } + @Test public void getWhenValidTokenAndPublicKeyInLambdaThenReturnsOk() { this.spring.register(PublicKeyInLambdaConfig.class, RootController.class).autowire(); From a9a9c2c0fd8ea85bf4201bd21aed808a42dddda0 Mon Sep 17 00:00:00 2001 From: Martin Nemec Date: Thu, 26 Mar 2020 18:30:28 +0100 Subject: [PATCH 062/348] OAuth2 ClientRegistrations NPE fix when userinfo missing Fixes gh-8187 --- .../oauth2/client/registration/ClientRegistrations.java | 9 ++++++--- .../client/registration/ClientRegistrationsTest.java | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java index 57d10aaadc..c6cbaebf55 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java @@ -146,9 +146,12 @@ public final class ClientRegistrations { RequestEntity request = RequestEntity.get(uri).build(); Map configuration = rest.exchange(request, typeReference).getBody(); OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse); - return withProviderConfiguration(metadata, issuer.toASCIIString()) - .jwkSetUri(metadata.getJWKSetURI().toASCIIString()) - .userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString()); + ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString()) + .jwkSetUri(metadata.getJWKSetURI().toASCIIString()); + if (metadata.getUserInfoEndpointURI() != null) { + builder.userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString()); + } + return builder; }; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java index 3897870e70..f0bc773773 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java @@ -195,6 +195,14 @@ public class ClientRegistrationsTest { assertThat(provider.getJwkSetUri()).isNull(); } + // gh-8187 + @Test + public void issuerWhenResponseMissingUserInfoUriThenSuccess() throws Exception { + this.response.remove("userinfo_endpoint"); + ClientRegistration registration = registration("").build(); + assertThat(registration.getProviderDetails().getUserInfoEndpoint().getUri()).isNull(); + } + @Test public void issuerWhenContainsTrailingSlashThenSuccess() throws Exception { assertThat(registration("")).isNotNull(); From 01f8eb3961ab16107448e39932f850c0f2ea71df Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 27 Mar 2020 09:47:53 -0400 Subject: [PATCH 063/348] Update Encryptors documentation Fixes gh-8208 --- .../security/crypto/encrypt/Encryptors.java | 11 ++++++++--- .../asciidoc/_includes/servlet/crypto/index.adoc | 13 ++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java index 7e0c5e8295..aee376b702 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java +++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java @@ -39,9 +39,6 @@ public class Encryptors { * not be shared * @param salt a hex-encoded, random, site-global salt value to use to generate the * key - * - * @see #standard(CharSequence, CharSequence) which uses the slightly weaker CBC mode - * (instead of GCM) */ public static BytesEncryptor stronger(CharSequence password, CharSequence salt) { return new AesBytesEncryptor(password.toString(), salt, @@ -55,11 +52,19 @@ public class Encryptors { * provided salt is expected to be hex-encoded; it should be random and at least 8 * bytes in length. Also applies a random 16 byte initialization vector to ensure each * encrypted message will be unique. Requires Java 6. + * NOTE: This mode is not + * authenticated + * and does not provide any guarantees about the authenticity of the data. + * For a more secure alternative, users should prefer + * {@link #stronger(CharSequence, CharSequence)}. * * @param password the password used to generate the encryptor's secret key; should * not be shared * @param salt a hex-encoded, random, site-global salt value to use to generate the * key + * + * @see #stronger(CharSequence, CharSequence) which uses the significatly more secure + * GCM (instead of CBC) */ public static BytesEncryptor standard(CharSequence password, CharSequence salt) { return new AesBytesEncryptor(password.toString(), salt, diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc index 753c46c4b1..0ccfd5c605 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/crypto/index.adoc @@ -17,14 +17,16 @@ Encryptors are thread-safe. [[spring-security-crypto-encryption-bytes]] === BytesEncryptor -Use the Encryptors.standard factory method to construct a "standard" BytesEncryptor: +Use the `Encryptors.stronger` factory method to construct a BytesEncryptor: [source,java] ---- -Encryptors.standard("password", "salt"); +Encryptors.stronger("password", "salt"); ---- -The "standard" encryption method is 256-bit AES using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2). +The "stronger" encryption method creates an encryptor using 256 bit AES encryption with +Galois Counter Mode (GCM). +It derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation Function #2). This method requires Java 6. The password used to generate the SecretKey should be kept in a secure place and not be shared. The salt is used to prevent dictionary attacks against the key in the event your encrypted data is compromised. @@ -38,6 +40,11 @@ Such a salt may be generated using a KeyGenerator: String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded ---- +Users may also use the `standard` encryption method, which is 256-bit AES in Cipher Block Chaining (CBC) Mode. +This mode is not https://en.wikipedia.org/wiki/Authenticated_encryption[authenticated] and does not provide any +guarantees about the authenticity of the data. +For a more secure alternative, users should prefer `Encryptors.stronger`. + [[spring-security-crypto-encryption-text]] === TextEncryptor Use the Encryptors.text factory method to construct a standard TextEncryptor: From 258627eaee5faf9c0ec8b1d15a024350193da814 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 27 Mar 2020 13:41:49 -0600 Subject: [PATCH 064/348] SwitchUserFilter Defaults to POST Fixes gh-4183 --- .../switchuser/SwitchUserFilter.java | 2 +- .../switchuser/SwitchUserFilterTests.java | 46 +++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java index c460f5a522..e7863b90ac 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java @@ -563,6 +563,6 @@ public class SwitchUserFilter extends GenericFilterBean } private static RequestMatcher createMatcher(String pattern) { - return new AntPathRequestMatcher(pattern, null, true, new UrlPathHelper()); + return new AntPathRequestMatcher(pattern, "POST", true, new UrlPathHelper()); } } diff --git a/web/src/test/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilterTests.java b/web/src/test/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilterTests.java index af2e24a76d..aa1affbc16 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilterTests.java @@ -16,11 +16,16 @@ package org.springframework.security.web.authentication.switchuser; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.FilterChain; -import org.junit.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import org.junit.rules.ExpectedException; + import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.authentication.AccountExpiredException; @@ -42,8 +47,10 @@ import org.springframework.security.web.DefaultRedirectStrategy; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import javax.servlet.FilterChain; -import java.util.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; /** * Tests @@ -75,6 +82,7 @@ public class SwitchUserFilterTests { request.setScheme("http"); request.setServerName("localhost"); request.setRequestURI("/login/impersonate"); + request.setMethod("POST"); return request; } @@ -125,6 +133,20 @@ public class SwitchUserFilterTests { assertThat(filter.requiresExitUser(request)).isFalse(); } + @Test + // gh-4183 + public void requiresExitUserWhenGetThenDoesNotMatch() { + SwitchUserFilter filter = new SwitchUserFilter(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setScheme("http"); + request.setServerName("localhost"); + request.setRequestURI("/login/impersonate"); + request.setMethod("GET"); + + assertThat(filter.requiresExitUser(request)).isFalse(); + } + @Test public void requiresExitUserWhenMatcherThenWorks() { SwitchUserFilter filter = new SwitchUserFilter(); @@ -159,6 +181,20 @@ public class SwitchUserFilterTests { assertThat(filter.requiresSwitchUser(request)).isFalse(); } + @Test + // gh-4183 + public void requiresSwitchUserWhenGetThenDoesNotMatch() { + SwitchUserFilter filter = new SwitchUserFilter(); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setScheme("http"); + request.setServerName("localhost"); + request.setRequestURI("/login/impersonate"); + request.setMethod("GET"); + + assertThat(filter.requiresSwitchUser(request)).isFalse(); + } + @Test public void requiresSwitchUserWhenMatcherThenWorks() { SwitchUserFilter filter = new SwitchUserFilter(); From 401597c6738b12b9bfb2601b1ed841ca424bebd5 Mon Sep 17 00:00:00 2001 From: Ruby Hartono <58564005+rh-id@users.noreply.github.com> Date: Thu, 19 Mar 2020 19:54:12 +0700 Subject: [PATCH 065/348] Improve OAuth2LoginAuthenticationProvider 1. update OAuth2LoginAuthenticationProvider to use OAuth2AuthorizationCodeAuthenticationProvider 2. apply fix gh-5368 for OAuth2AuthorizationCodeAuthenticationProvider to return additionalParameters value from accessTokenResponse Fixes gh-5633 --- ...thorizationCodeAuthenticationProvider.java | 5 ++- .../OAuth2LoginAuthenticationProvider.java | 40 ++++++++----------- ...zationCodeAuthenticationProviderTests.java | 26 +++++++++++- 3 files changed, 45 insertions(+), 26 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java index 938fd9c215..fc5fb598b3 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.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. @@ -73,7 +73,8 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica authorizationCodeAuthentication.getClientRegistration(), authorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(), - accessTokenResponse.getRefreshToken()); + accessTokenResponse.getRefreshToken(), + accessTokenResponse.getAdditionalParameters()); authenticationResult.setDetails(authorizationCodeAuthentication.getDetails()); return authenticationResult; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java index 02d4b5e7b1..8d3b70234d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.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. @@ -28,7 +28,6 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.util.Assert; @@ -60,7 +59,7 @@ import java.util.Map; * @see Section 4.1.4 Access Token Response */ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider { - private final OAuth2AccessTokenResponseClient accessTokenResponseClient; + private final OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider; private final OAuth2UserService userService; private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities); @@ -74,59 +73,54 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider OAuth2AccessTokenResponseClient accessTokenResponseClient, OAuth2UserService userService) { - Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null"); Assert.notNull(userService, "userService cannot be null"); - this.accessTokenResponseClient = accessTokenResponseClient; + this.authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(accessTokenResponseClient); this.userService = userService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { - OAuth2LoginAuthenticationToken authorizationCodeAuthentication = + OAuth2LoginAuthenticationToken loginAuthenticationToken = (OAuth2LoginAuthenticationToken) authentication; // Section 3.1.2.1 Authentication Request - https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest // scope // REQUIRED. OpenID Connect requests MUST contain the "openid" scope value. - if (authorizationCodeAuthentication.getAuthorizationExchange() + if (loginAuthenticationToken.getAuthorizationExchange() .getAuthorizationRequest().getScopes().contains("openid")) { // This is an OpenID Connect Authentication Request so return null // and let OidcAuthorizationCodeAuthenticationProvider handle it instead return null; } - OAuth2AccessTokenResponse accessTokenResponse; + OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken; try { - OAuth2AuthorizationExchangeValidator.validate( - authorizationCodeAuthentication.getAuthorizationExchange()); - - accessTokenResponse = this.accessTokenResponseClient.getTokenResponse( - new OAuth2AuthorizationCodeGrantRequest( - authorizationCodeAuthentication.getClientRegistration(), - authorizationCodeAuthentication.getAuthorizationExchange())); - + authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider + .authenticate(new OAuth2AuthorizationCodeAuthenticationToken( + loginAuthenticationToken.getClientRegistration(), + loginAuthenticationToken.getAuthorizationExchange())); } catch (OAuth2AuthorizationException ex) { OAuth2Error oauth2Error = ex.getError(); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } - OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken(); - Map additionalParameters = accessTokenResponse.getAdditionalParameters(); + OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken(); + Map additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters(); OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest( - authorizationCodeAuthentication.getClientRegistration(), accessToken, additionalParameters)); + loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters)); Collection mappedAuthorities = this.authoritiesMapper.mapAuthorities(oauth2User.getAuthorities()); OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken( - authorizationCodeAuthentication.getClientRegistration(), - authorizationCodeAuthentication.getAuthorizationExchange(), + loginAuthenticationToken.getClientRegistration(), + loginAuthenticationToken.getAuthorizationExchange(), oauth2User, mappedAuthorities, accessToken, - accessTokenResponse.getRefreshToken()); - authenticationResult.setDetails(authorizationCodeAuthentication.getDetails()); + authorizationCodeAuthenticationToken.getRefreshToken()); + authenticationResult.setDetails(loginAuthenticationToken.getDetails()); return authenticationResult; } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java index 2ba5e36927..41ebe4a1e6 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -16,6 +16,8 @@ package org.springframework.security.oauth2.client.authentication; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -119,4 +121,26 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests { assertThat(authenticationResult.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); assertThat(authenticationResult.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken()); } + + // gh-5368 + @Test + public void authenticateWhenAuthorizationSuccessResponseThenAdditionalParametersIncluded() { + Map additionalParameters = new HashMap<>(); + additionalParameters.put("param1", "value1"); + additionalParameters.put("param2", "value2"); + + OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponse().additionalParameters(additionalParameters) + .build(); + when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(accessTokenResponse); + + OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(this.authorizationRequest, + success().build()); + + OAuth2AuthorizationCodeAuthenticationToken authentication = (OAuth2AuthorizationCodeAuthenticationToken) this.authenticationProvider + .authenticate( + new OAuth2AuthorizationCodeAuthenticationToken(this.clientRegistration, authorizationExchange)); + + assertThat(authentication.getAdditionalParameters()) + .containsAllEntriesOf(accessTokenResponse.getAdditionalParameters()); + } } From 32c335392187b5802107997b64a299fb8ef6771a Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 30 Mar 2020 16:04:13 -0500 Subject: [PATCH 066/348] SpringTestContext returns ConfigurableWebApplicationContext Closes gh-8233 --- .../security/config/test/SpringTestContext.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java index 4909d66107..09d580acc7 100644 --- a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java +++ b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java @@ -17,7 +17,6 @@ package org.springframework.security.config.test; import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; @@ -113,8 +112,10 @@ public class SpringTestContext implements Closeable { return this; } - public ConfigurableApplicationContext getContext() { + public ConfigurableWebApplicationContext getContext() { if (!this.context.isRunning()) { + this.context.setServletContext(new MockServletContext()); + this.context.setServletConfig(new MockServletConfig()); this.context.refresh(); } return this.context; From 615f9a3f05e76d485b2ca412af05c7beede414f2 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 30 Mar 2020 16:18:02 -0500 Subject: [PATCH 067/348] Fix HttpServlet3RequestFactory Logout Handlers Previously there was a problem with Servlet API logout integration when Servlet API was configured before log out. This ensures that logout handlers is a reference to the logout handlers vs copying the logout handlers. This ensures that the ordering does not matter. Closes gh-4760 --- .../ServletApiConfigurerTests.java | 39 +++++++++++++++++++ .../HttpServlet3RequestFactory.java | 13 ++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java index dd3561d9fb..7a9e5b1f94 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java @@ -22,6 +22,7 @@ import org.junit.Rule; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationTrustResolver; @@ -44,10 +45,14 @@ import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.ConfigurableWebApplicationContext; import javax.servlet.Filter; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -60,6 +65,7 @@ import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -329,6 +335,39 @@ public class ServletApiConfigurerTests { } } + @Test + public void logoutServletApiWhenCsrfDisabled() throws Exception { + ConfigurableWebApplicationContext context = this.spring.register(CsrfDisabledConfig.class).getContext(); + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context) + .apply(springSecurity()) + .build(); + MvcResult mvcResult = mockMvc.perform(get("/")) + .andReturn(); + assertThat(mvcResult.getRequest().getSession(false)).isNull(); + } + + @Configuration + @EnableWebSecurity + static class CsrfDisabledConfig extends WebSecurityConfigurerAdapter { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .csrf().disable(); + // @formatter:on + } + + @RestController + static class LogoutController { + @GetMapping("/") + String logout(HttpServletRequest request) throws ServletException { + request.getSession().setAttribute("foo", "bar"); + request.logout(); + return "logout"; + } + } + } + private T getFilter(Class filterClass) { return (T) getFilters().stream() .filter(filterClass::isInstance) diff --git a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java index 1812e637f4..1602016b20 100644 --- a/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java +++ b/web/src/main/java/org/springframework/security/web/servletapi/HttpServlet3RequestFactory.java @@ -42,7 +42,6 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -80,7 +79,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); private AuthenticationEntryPoint authenticationEntryPoint; private AuthenticationManager authenticationManager; - private LogoutHandler logoutHandler; + private List logoutHandlers; HttpServlet3RequestFactory(String rolePrefix) { this.rolePrefix = rolePrefix; @@ -144,7 +143,7 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { * {@link HttpServletRequest#logout()}. */ public void setLogoutHandlers(List logoutHandlers) { - this.logoutHandler = CollectionUtils.isEmpty(logoutHandlers) ? null : new CompositeLogoutHandler(logoutHandlers); + this.logoutHandlers = logoutHandlers; } /** @@ -244,8 +243,8 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { @Override public void logout() throws ServletException { - LogoutHandler handler = HttpServlet3RequestFactory.this.logoutHandler; - if (handler == null) { + List handlers = HttpServlet3RequestFactory.this.logoutHandlers; + if (CollectionUtils.isEmpty(handlers)) { HttpServlet3RequestFactory.this.logger.debug( "logoutHandlers is null, so allowing original HttpServletRequest to handle logout"); super.logout(); @@ -253,7 +252,9 @@ final class HttpServlet3RequestFactory implements HttpServletRequestFactory { } Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); - handler.logout(this, this.response, authentication); + for (LogoutHandler handler : handlers) { + handler.logout(this, this.response, authentication); + } } private boolean isAuthenticated() { From 298d40b7a562d807250ab1151584ce1a5b257074 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 31 Mar 2020 18:05:27 -0400 Subject: [PATCH 068/348] Update tests to use absolute paths Fixes gh-8260 --- .../security/integration/BasicAuthenticationTests.java | 2 +- .../security/integration/ConcurrentSessionManagementTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/itest/web/src/integration-test/java/org/springframework/security/integration/BasicAuthenticationTests.java b/itest/web/src/integration-test/java/org/springframework/security/integration/BasicAuthenticationTests.java index 5beba7b3e8..4ea924f968 100644 --- a/itest/web/src/integration-test/java/org/springframework/security/integration/BasicAuthenticationTests.java +++ b/itest/web/src/integration-test/java/org/springframework/security/integration/BasicAuthenticationTests.java @@ -28,7 +28,7 @@ public class BasicAuthenticationTests extends AbstractWebServerIntegrationTests @Test public void httpBasicWhenAuthenticationRequiredAndNotAuthenticatedThen401() throws Exception { MockMvc mockMvc = createMockMvc("classpath:/spring/http-security-basic.xml", "classpath:/spring/in-memory-provider.xml", "classpath:/spring/testapp-servlet.xml"); - mockMvc.perform(get("secure/index")) + mockMvc.perform(get("/secure/index")) .andExpect(status().isUnauthorized()); } diff --git a/itest/web/src/integration-test/java/org/springframework/security/integration/ConcurrentSessionManagementTests.java b/itest/web/src/integration-test/java/org/springframework/security/integration/ConcurrentSessionManagementTests.java index 335f957ef3..18d26e7ae0 100644 --- a/itest/web/src/integration-test/java/org/springframework/security/integration/ConcurrentSessionManagementTests.java +++ b/itest/web/src/integration-test/java/org/springframework/security/integration/ConcurrentSessionManagementTests.java @@ -47,7 +47,7 @@ public class ConcurrentSessionManagementTests extends AbstractWebServerIntegrati MockMvc mockMvc = createMockMvc("classpath:/spring/http-security-concurrency.xml", "classpath:/spring/in-memory-provider.xml", "classpath:/spring/testapp-servlet.xml"); - mockMvc.perform(get("secure/index").session(session1)) + mockMvc.perform(get("/secure/index").session(session1)) .andExpect(status().is3xxRedirection()); MockHttpServletRequestBuilder login1 = login() From 6a9585dc0d7dd89c7af9d9b21867aec299d1b783 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 31 Mar 2020 18:06:26 -0400 Subject: [PATCH 069/348] Update to Spring Framework 5.2.5 Fixes gh-8249 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 4dbec16b08..ed9a7a7f82 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.3.RELEASE' + ext.springVersion = '5.2.5.RELEASE' } if (!project.hasProperty('springDataVersion')) { From 774dd7bb13b5ce360cd03e49d0840d03184b6bc1 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:09:06 -0400 Subject: [PATCH 070/348] Update to Spring Data Moore-SR6 Fixes gh-8248 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ed9a7a7f82..cab13905ce 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR3' + ext.springDataVersion = 'Moore-SR6' } ext.rsocketVersion = '1.0.0-RC5' From dbdf02e86441efdc33e85a3fd3c75dc9bded68c0 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:09:42 -0400 Subject: [PATCH 071/348] Update to Reactor Dysprosium-SR6 Fixes gh-8250 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index cab13905ce..31a539e5b7 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR4' + ext.reactorVersion = 'Dysprosium-SR6' } if (!project.hasProperty('springVersion')) { From 1ca3ea3a7135bd69ce80392577d730313417b624 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:10:43 -0400 Subject: [PATCH 072/348] Update to GAE 1.9.79 Fixes gh-8251 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 10ee0508ed..a2ce714b59 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 -gaeVersion=1.9.76 +gaeVersion=1.9.79 springBootVersion=2.2.4.RELEASE version=5.2.3.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From e3c3601cf67aca876a45c9be52c4a0be9ba1ee21 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:11:30 -0400 Subject: [PATCH 073/348] Update to Spring Boot 2.2.6.RELEASE Fixes gh-8252 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a2ce714b59..35671eabd7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.79 -springBootVersion=2.2.4.RELEASE +springBootVersion=2.2.6.RELEASE version=5.2.3.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 08b635a85a1ad1ca98c8e44651bb44c67bcf7e00 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:12:07 -0400 Subject: [PATCH 074/348] Update to org.powermock 2.0.6 Fixes gh-8255 --- gradle/dependency-management.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 31a539e5b7..ba70580f91 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -24,12 +24,12 @@ dependencyManagement { dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' - dependency 'org.powermock:powermock-api-mockito2:2.0.5' - dependency 'org.powermock:powermock-api-support:2.0.5' - dependency 'org.powermock:powermock-core:2.0.5' - dependency 'org.powermock:powermock-module-junit4-common:2.0.5' - dependency 'org.powermock:powermock-module-junit4:2.0.5' - dependency 'org.powermock:powermock-reflect:2.0.5' + dependency 'org.powermock:powermock-api-mockito2:2.0.6' + dependency 'org.powermock:powermock-api-support:2.0.6' + dependency 'org.powermock:powermock-core:2.0.6' + dependency 'org.powermock:powermock-module-junit4-common:2.0.6' + dependency 'org.powermock:powermock-module-junit4:2.0.6' + dependency 'org.powermock:powermock-reflect:2.0.6' dependency 'org.python:jython:2.5.0' dependency 'org.spockframework:spock-core:1.0-groovy-2.4' dependency 'org.spockframework:spock-spring:1.0-groovy-2.4' From eacede6171100778dc9bffd627364537478ea919 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:12:31 -0400 Subject: [PATCH 075/348] Update to httpclient 4.5.12 Fixes gh-8253 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ba70580f91..40bf869681 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -138,7 +138,7 @@ dependencyManagement { dependency 'org.apache.directory.shared:shared-cursor:0.9.15' dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15' dependency 'org.apache.directory.shared:shared-ldap:0.9.15' - dependency 'org.apache.httpcomponents:httpclient:4.5.11' + dependency 'org.apache.httpcomponents:httpclient:4.5.12' dependency 'org.apache.httpcomponents:httpcore:4.4.8' dependency 'org.apache.httpcomponents:httpmime:4.5.3' dependency 'org.apache.mina:mina-core:2.0.0-M6' From a02cd9d9e3587cc74c66bf6845d296835be250be Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:13:20 -0400 Subject: [PATCH 076/348] Upgrade to embedded Apache Tomcat 9.0.33 Fixes gh-8254 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 40bf869681..946e46fba1 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.24' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.24' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.24' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.24' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.33' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.33' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.33' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.33' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.24' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.33' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From a57f4bb025d171c006deda28f82476b2f1d13475 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:14:38 -0400 Subject: [PATCH 077/348] Update RSocket to 1.0.0-RC6 Fixes gh-8256 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 946e46fba1..dbbd7a6609 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR6' } -ext.rsocketVersion = '1.0.0-RC5' +ext.rsocketVersion = '1.0.0-RC6' dependencyManagement { imports { From a021cd3fd5fb4e2024afb71ebbac5c9515e160a3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:15:12 -0400 Subject: [PATCH 078/348] Update to mockwebserver 3.14.7 Fixes gh-8257 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index dbbd7a6609..79b02e73ca 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -20,7 +20,7 @@ dependencyManagement { } dependencies { dependency 'cglib:cglib-nodep:3.3.0' - dependency 'com.squareup.okhttp3:mockwebserver:3.14.6' + dependency 'com.squareup.okhttp3:mockwebserver:3.14.7' dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' From 03137578460ef252478e1284b473ec7955030096 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:15:38 -0400 Subject: [PATCH 079/348] Update to okhttp 3.14.7 Fixes gh-8259 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 79b02e73ca..bc49067e46 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -58,7 +58,7 @@ dependencyManagement { dependency 'com.nimbusds:lang-tag:1.4.3' dependency 'com.nimbusds:nimbus-jose-jwt:7.8.1' dependency 'com.nimbusds:oauth2-oidc-sdk:6.14' - dependency 'com.squareup.okhttp3:okhttp:3.14.6' + dependency 'com.squareup.okhttp3:okhttp:3.14.7' dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-impl:2.3.2' From 7018e7ec51ca3af71df6951a0197e5e85b641c83 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:16:29 -0400 Subject: [PATCH 080/348] Update to Jackson 2.10.3 Fixes gh-8258 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index bc49067e46..36391cecda 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -42,9 +42,9 @@ dependencyManagement { dependency 'asm:asm:3.1' dependency 'ch.qos.logback:logback-classic:1.2.3' dependency 'ch.qos.logback:logback-core:1.2.3' - dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.2' - dependency 'com.fasterxml.jackson.core:jackson-core:2.10.2' - dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.2' + dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.3' + dependency 'com.fasterxml.jackson.core:jackson-core:2.10.3' + dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.3' dependency 'com.fasterxml:classmate:1.3.4' dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1' dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion' From 97f1e368ee14bcbca371ceb133402d7c700aef10 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:58:19 -0400 Subject: [PATCH 081/348] Update to reactive-streams 1.0.3 Fixes gh-8279 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 36391cecda..14fb6ad093 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -197,7 +197,7 @@ dependencyManagement { dependency 'org.opensaml:opensaml-saml-api:3.4.3' dependency 'org.opensaml:opensaml-saml-impl:3.4.3' dependency 'org.ow2.asm:asm:6.2.1' - dependency 'org.reactivestreams:reactive-streams:1.0.1' + dependency 'org.reactivestreams:reactive-streams:1.0.3' dependency 'org.seleniumhq.selenium:htmlunit-driver:2.36.0' dependency 'org.seleniumhq.selenium:selenium-java:3.141.59' dependency 'org.seleniumhq.selenium:selenium-support:3.141.59' From dd6974812fb0ae8330f6f422ad16a5c27e5ebb68 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:59:33 -0400 Subject: [PATCH 082/348] Update to unboundid-ldapsdk 4.0.14 Fixes gh-8274 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 14fb6ad093..969015d3d7 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -62,7 +62,7 @@ dependencyManagement { dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-impl:2.3.2' - dependency 'com.unboundid:unboundid-ldapsdk:4.0.12' + dependency 'com.unboundid:unboundid-ldapsdk:4.0.14' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'commons-cli:commons-cli:1.4' dependency 'commons-codec:commons-codec:1.14' From 433646b55124ed1ebcab164712a868b00e0d8d97 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 09:59:58 -0400 Subject: [PATCH 083/348] Update blockhound to 1.0.3.RELEASE Fixes gh-8275 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 969015d3d7..e447214df2 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -72,7 +72,7 @@ dependencyManagement { dependency 'commons-lang:commons-lang:2.6' dependency 'commons-logging:commons-logging:1.2' dependency 'dom4j:dom4j:1.6.1' - dependency 'io.projectreactor.tools:blockhound:1.0.1.RELEASE' + dependency 'io.projectreactor.tools:blockhound:1.0.3.RELEASE' dependency "io.rsocket:rsocket-core:${rsocketVersion}" dependency "io.rsocket:rsocket-transport-netty:${rsocketVersion}" dependency 'javax.activation:activation:1.1.1' From ed133d4b8ddfee83715a0ff747319e302b86abd4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 10:00:41 -0400 Subject: [PATCH 084/348] Update to hibernate-core 5.2.18.Final Fixes gh-8276 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e447214df2..1a35ccefb6 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -181,7 +181,7 @@ dependencyManagement { dependency 'org.hamcrest:hamcrest-core:1.3' dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' - dependency 'org.hibernate:hibernate-core:5.2.17.Final' + dependency 'org.hibernate:hibernate-core:5.2.18.Final' dependency 'org.hibernate:hibernate-entitymanager:5.4.10.Final' dependency 'org.hibernate:hibernate-validator:6.1.2.Final' dependency 'org.hsqldb:hsqldb:2.5.0' From 80a16e828f1b1426d79fc6a0ec5bbd818b421e2d Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 10:01:20 -0400 Subject: [PATCH 085/348] Update to hibernate-entitymanager 5.4.13.Final Fixes gh-8277 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 1a35ccefb6..bd1be4321c 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.10.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.13.Final' dependency 'org.hibernate:hibernate-validator:6.1.2.Final' dependency 'org.hsqldb:hsqldb:2.5.0' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From 7b5bdac74d22d3a581c9c0ae21c164de4f37f952 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 10:01:53 -0400 Subject: [PATCH 086/348] Update to OpenSAML 3.4.5 Fixes gh-8278 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index bd1be4321c..17e8e0b783 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -193,9 +193,9 @@ dependencyManagement { dependency 'org.mockito:mockito-core:3.0.0' dependency 'org.objenesis:objenesis:2.6' dependency 'org.openid4java:openid4java-nodeps:0.9.6' - dependency 'org.opensaml:opensaml-core:3.4.3' - dependency 'org.opensaml:opensaml-saml-api:3.4.3' - dependency 'org.opensaml:opensaml-saml-impl:3.4.3' + dependency 'org.opensaml:opensaml-core:3.4.5' + dependency 'org.opensaml:opensaml-saml-api:3.4.5' + dependency 'org.opensaml:opensaml-saml-impl:3.4.5' dependency 'org.ow2.asm:asm:6.2.1' dependency 'org.reactivestreams:reactive-streams:1.0.3' dependency 'org.seleniumhq.selenium:htmlunit-driver:2.36.0' From f93e5afe163b52a46258a697bf75364d81e06668 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 12:35:09 -0400 Subject: [PATCH 087/348] Release 5.2.3.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 35671eabd7..9b69f0a875 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.79 springBootVersion=2.2.6.RELEASE -version=5.2.3.BUILD-SNAPSHOT +version=5.2.3.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 30a63026f895a6dd0d942c5b9c6c12b9ff13f2e2 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 1 Apr 2020 14:26:04 -0400 Subject: [PATCH 088/348] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9b69f0a875..a9a9095b38 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.79 springBootVersion=2.2.6.RELEASE -version=5.2.3.RELEASE +version=5.2.4.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From bb654fdcdfa32a30031b5a450cdfc46770636455 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 2 Apr 2020 11:32:38 -0400 Subject: [PATCH 089/348] Fix HttpSecurity Javadoc Fixes gh-4404 --- .../security/config/annotation/web/builders/HttpSecurity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index aab7f31d09..45e8568872 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -1105,6 +1105,7 @@ public final class HttpSecurity extends /** * Allows restricting access based upon the {@link HttpServletRequest} using + * {@link RequestMatcher} implementations (i.e. via URL patterns). * *

Example Configurations

* From f011c36ba41f15e1e41ccda6207411c6b489a5be Mon Sep 17 00:00:00 2001 From: hotire Date: Tue, 7 Apr 2020 00:06:28 +0900 Subject: [PATCH 090/348] Fix typo in Javadoc of ServerHttpSecurity#hasAuthority Closes gh-8336 --- .../security/config/web/server/ServerHttpSecurity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 22f73ba314..52e253bc61 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -2649,7 +2649,7 @@ public class ServerHttpSecurity { /** * Require a specific authority. - * @param authority the authority to require (i.e. "USER" woudl require authority of "USER"). + * @param authority the authority to require (i.e. "USER" would require authority of "USER"). * @return the {@link AuthorizeExchangeSpec} to configure */ public AuthorizeExchangeSpec hasAuthority(String authority) { From 564d5644c993f622db8bc22737471c57dd08d166 Mon Sep 17 00:00:00 2001 From: Alan Czajkowski Date: Sat, 4 Apr 2020 12:46:11 -0400 Subject: [PATCH 091/348] BCryptPasswordEncoder rawPassword cannot be null Closes gh-8317 --- .../crypto/bcrypt/BCryptPasswordEncoder.java | 8 ++++++++ .../crypto/bcrypt/BCryptPasswordEncoderTests.java | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java index c59246320d..dd787a9ea4 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.java @@ -99,6 +99,10 @@ public class BCryptPasswordEncoder implements PasswordEncoder { } public String encode(CharSequence rawPassword) { + if (rawPassword == null) { + throw new IllegalArgumentException("rawPassword cannot be null"); + } + String salt; if (random != null) { salt = BCrypt.gensalt(version.getVersion(), strength, random); @@ -109,6 +113,10 @@ public class BCryptPasswordEncoder implements PasswordEncoder { } public boolean matches(CharSequence rawPassword, String encodedPassword) { + if (rawPassword == null) { + throw new IllegalArgumentException("rawPassword cannot be null"); + } + if (encodedPassword == null || encodedPassword.length() == 0) { logger.warn("Empty encoded password"); return false; diff --git a/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java index 28ac723bce..1ae357f019 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoderTests.java @@ -200,4 +200,16 @@ public class BCryptPasswordEncoderTests { encoder.upgradeEncoding("not-a-bcrypt-password"); } + @Test(expected = IllegalArgumentException.class) + public void encodeNullRawPassword() { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + encoder.encode(null); + } + + @Test(expected = IllegalArgumentException.class) + public void matchNullRawPassword() { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + encoder.matches(null, "does-not-matter"); + } + } From 52ed597f4c81b7c17e5709804c4204f47d3694e7 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 8 Apr 2020 09:08:30 -0500 Subject: [PATCH 092/348] Fix example in javadoc of FilterChainProxy Closes gh-8344 --- .../java/org/springframework/security/web/FilterChainProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java index b4157c8921..d0d348b751 100644 --- a/web/src/main/java/org/springframework/security/web/FilterChainProxy.java +++ b/web/src/main/java/org/springframework/security/web/FilterChainProxy.java @@ -60,7 +60,7 @@ import java.util.*; * requests which match the pattern. An example configuration might look like this: * *
- *  <bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
+ *  <bean id="myfilterChainProxy" class="org.springframework.security.web.FilterChainProxy">
  *      <constructor-arg>
  *          <util:list>
  *              <security:filter-chain pattern="/do/not/filter*" filters="none"/>

From 7b34b223e6582d7135cca753a7495f62b5a9477d Mon Sep 17 00:00:00 2001
From: Rob Winch 
Date: Mon, 13 Apr 2020 12:32:59 -0500
Subject: [PATCH 093/348] Logout defaults to use Global
 SecurityContextServerLogoutHandler

Closes gh-8375
---
 .../config/web/server/ServerHttpSecurity.java |  7 ++-
 .../config/web/server/LogoutSpecTests.java    | 43 +++++++++++++++++++
 .../config/web/server/OAuth2LoginTests.java   |  3 ++
 3 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
index 52e253bc61..d60654c066 100644
--- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
+++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
@@ -3727,7 +3727,8 @@ public class ServerHttpSecurity {
 	 */
 	public final class LogoutSpec {
 		private LogoutWebFilter logoutWebFilter = new LogoutWebFilter();
-		private List logoutHandlers = new ArrayList<>(Arrays.asList(new SecurityContextServerLogoutHandler()));
+		private final SecurityContextServerLogoutHandler DEFAULT_LOGOUT_HANDLER = new SecurityContextServerLogoutHandler();
+		private List logoutHandlers = new ArrayList<>(Arrays.asList(this.DEFAULT_LOGOUT_HANDLER));
 
 		/**
 		 * Configures the logout handler. Default is {@code SecurityContextServerLogoutHandler}
@@ -3791,6 +3792,10 @@ public class ServerHttpSecurity {
 		}
 
 		private ServerLogoutHandler createLogoutHandler() {
+			ServerSecurityContextRepository securityContextRepository = ServerHttpSecurity.this.securityContextRepository;
+			if (securityContextRepository != null) {
+				this.DEFAULT_LOGOUT_HANDLER.setSecurityContextRepository(securityContextRepository);
+			}
 			if (this.logoutHandlers.isEmpty()) {
 				return null;
 			} else if (this.logoutHandlers.size() == 1) {
diff --git a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
index e417a3cda5..723251e4cf 100644
--- a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
+++ b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java
@@ -21,6 +21,7 @@ import org.openqa.selenium.WebDriver;
 import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
 import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
 import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
 import org.springframework.test.web.reactive.server.WebTestClient;
 import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
@@ -200,4 +201,46 @@ public class LogoutSpecTests {
 		homePage
 				.assertAt();
 	}
+
+
+	@Test
+	public void logoutWhenCustomSecurityContextRepositoryThenLogsOut() {
+		WebSessionServerSecurityContextRepository repository = new WebSessionServerSecurityContextRepository();
+		repository.setSpringSecurityContextAttrName("CUSTOM_CONTEXT_ATTR");
+		SecurityWebFilterChain securityWebFilter = this.http
+				.securityContextRepository(repository)
+				.authorizeExchange()
+					.anyExchange().authenticated()
+					.and()
+				.formLogin()
+					.and()
+				.logout()
+					.and()
+				.build();
+
+		WebTestClient webTestClient = WebTestClientBuilder
+				.bindToWebFilters(securityWebFilter)
+				.build();
+
+		WebDriver driver = WebTestClientHtmlUnitDriverBuilder
+				.webTestClientSetup(webTestClient)
+				.build();
+
+		FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
+				.assertAt();
+
+		FormLoginTests.HomePage homePage = loginPage.loginForm()
+				.username("user")
+				.password("password")
+				.submit(FormLoginTests.HomePage.class);
+
+		homePage.assertAt();
+
+		FormLoginTests.DefaultLogoutPage.to(driver)
+				.assertAt()
+				.logout();
+
+		FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class)
+				.assertAt();
+	}
 }
diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
index c820748e57..4723da1806 100644
--- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
+++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java
@@ -83,6 +83,7 @@ import org.springframework.security.web.server.authentication.RedirectServerAuth
 import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
 import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler;
 import org.springframework.security.web.server.context.ServerSecurityContextRepository;
 import org.springframework.security.web.server.savedrequest.ServerRequestCache;
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
@@ -716,6 +717,8 @@ public class OAuth2LoginTests {
 			http
 				.csrf().disable()
 				.logout()
+					// avoid using mock ServerSecurityContextRepository for logout
+					.logoutHandler(new SecurityContextServerLogoutHandler())
 					.logoutSuccessHandler(
 							new OidcClientInitiatedServerLogoutSuccessHandler(
 									new InMemoryReactiveClientRegistrationRepository(this.withLogout)))

From 067cb4579e3e3ad268f8b919451c64e5eda71e23 Mon Sep 17 00:00:00 2001
From: Josh Cummings 
Date: Fri, 17 Apr 2020 09:16:16 -0600
Subject: [PATCH 094/348] Polish OpenSamlAuthenticationProviderTests

- Add missing assertion

Issue gh-6019
---
 .../OpenSamlAuthenticationProviderTests.java               | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
index 387302323e..57ca4a8898 100644
--- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
+++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProviderTests.java
@@ -116,6 +116,13 @@ public class OpenSamlAuthenticationProviderTests {
 	@Test
 	public void authenticateWhenInvalidDestinationThenThrowAuthenticationException() {
 		Response response = response(recipientUri + "invalid", idpEntityId);
+		Assertion assertion = defaultAssertion();
+		signXmlObject(
+				assertion,
+				assertingPartyCredentials(),
+				recipientEntityId
+		);
+		response.getAssertions().add(assertion);
 		token = responseXml(response, idpEntityId);
 		exception.expect(authenticationMatcher(Saml2ErrorCodes.INVALID_DESTINATION));
 		provider.authenticate(token);

From 01f904895e227ee4e128e106430d73b660d678ae Mon Sep 17 00:00:00 2001
From: Souphorn 
Date: Fri, 17 Apr 2020 09:52:17 +0700
Subject: [PATCH 095/348] Fix typo with correct capitalization

Closes gh-8406
---
 .../asciidoc/_includes/servlet/authorization/architecture.adoc  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc
index b33d622e0b..c2bcddb059 100644
--- a/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc
+++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authorization/architecture.adoc
@@ -41,7 +41,7 @@ A pre-invocation decision on whether the invocation is allowed to proceed is mad
 [[authz-access-decision-manager]]
 ==== The AccessDecisionManager
 The `AccessDecisionManager` is called by the `AbstractSecurityInterceptor` and is responsible for making final access control decisions.
-the `AccessDecisionManager` interface contains three methods:
+The `AccessDecisionManager` interface contains three methods:
 
 [source,java]
 ----

From 9dd68f86d35b7e554204226bf81de3e9cfb95052 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Julian=20M=C3=BCller?= 
Date: Tue, 21 Apr 2020 17:18:55 +0200
Subject: [PATCH 096/348] Enables empty authorityPrefix

- docs stated that empty authorityPrefix are allowed but implementation denied to use `""`
- commit removes the `hasText`-limitation but restricts to `notNull`

Fixes gh-8421
---
 .../JwtGrantedAuthoritiesConverter.java       |  2 +-
 .../JwtGrantedAuthoritiesConverterTests.java  | 32 +++++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java
index 2803483a3e..2e9eaec2e9 100644
--- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java
+++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtGrantedAuthoritiesConverter.java
@@ -68,7 +68,7 @@ public final class JwtGrantedAuthoritiesConverter implements Converter authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
+
+		assertThat(authorities).containsExactly(
+				new SimpleGrantedAuthority("message:read"),
+				new SimpleGrantedAuthority("message:write"));
+	}
+
 	@Test
 	public void convertWhenTokenHasEmptyScopeAttributeThenTranslatedToNoAuthorities() {
 		Jwt jwt = jwt().claim("scope", "").build();
@@ -97,6 +116,19 @@ public class JwtGrantedAuthoritiesConverterTests {
 				new SimpleGrantedAuthority("ROLE_message:write"));
 	}
 
+	@Test
+	public void convertWithBlankAsCustomAuthorityPrefixWhenTokenHasScpAttributeThenTranslatedToAuthorities() {
+		Jwt jwt = jwt().claim("scp", "message:read message:write").build();
+
+		JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+		jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
+		Collection authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
+
+		assertThat(authorities).containsExactly(
+				new SimpleGrantedAuthority("message:read"),
+				new SimpleGrantedAuthority("message:write"));
+	}
+
 	@Test
 	public void convertWhenTokenHasEmptyScpAttributeThenTranslatedToNoAuthorities() {
 		Jwt jwt = jwt().claim("scp", Collections.emptyList()).build();

From 93a1fc104c865a1ae90ae9dd7cb01186d081cbfb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1cs?= 
Date: Sun, 19 Apr 2020 15:29:18 +0200
Subject: [PATCH 097/348] ActiveDirectoryLdapAuthenticationProvider uses
 InternalAuthenticationServiceException

Closes gh-2884
---
 .../security/messages.properties              |  1 +
 ...veDirectoryLdapAuthenticationProvider.java | 21 +++++++++---
 ...ectoryLdapAuthenticationProviderTests.java | 34 +++++++++++++++----
 3 files changed, 45 insertions(+), 11 deletions(-)

diff --git a/core/src/main/resources/org/springframework/security/messages.properties b/core/src/main/resources/org/springframework/security/messages.properties
index 665a742002..a84f259753 100644
--- a/core/src/main/resources/org/springframework/security/messages.properties
+++ b/core/src/main/resources/org/springframework/security/messages.properties
@@ -31,6 +31,7 @@ DigestAuthenticationFilter.usernameNotFound=Username {0} not found
 JdbcDaoImpl.noAuthority=User {0} has no GrantedAuthority
 JdbcDaoImpl.notFound=User {0} not found
 LdapAuthenticationProvider.badCredentials=Bad credentials
+LdapAuthenticationProvider.badLdapConnection=Connection to LDAP server failed
 LdapAuthenticationProvider.credentialsExpired=User credentials have expired
 LdapAuthenticationProvider.disabled=User is disabled
 LdapAuthenticationProvider.expired=User account has expired
diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java
index 91a41a0f29..afcb2fa87e 100644
--- a/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java
+++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProvider.java
@@ -16,6 +16,7 @@
 package org.springframework.security.ldap.authentication.ad;
 
 import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.ldap.CommunicationException;
 import org.springframework.ldap.core.DirContextOperations;
 import org.springframework.ldap.core.DistinguishedName;
 import org.springframework.ldap.core.support.DefaultDirObjectFactory;
@@ -24,6 +25,7 @@ import org.springframework.security.authentication.AccountExpiredException;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.CredentialsExpiredException;
 import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
 import org.springframework.security.authentication.LockedException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.GrantedAuthority;
@@ -142,12 +144,15 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
 			UsernamePasswordAuthenticationToken auth) {
 		String username = auth.getName();
 		String password = (String) auth.getCredentials();
-
-		DirContext ctx = bindAsUser(username, password);
+		DirContext ctx = null;
 
 		try {
+			ctx = bindAsUser(username, password);
 			return searchForUser(ctx, username);
 		}
+		catch (CommunicationException e) {
+			throw badLdapConnection(e);
+		}
 		catch (NamingException e) {
 			logger.error("Failed to locate directory entry for authenticated user: "
 					+ username, e);
@@ -210,8 +215,7 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
 					|| (e instanceof OperationNotSupportedException)) {
 				handleBindException(bindPrincipal, e);
 				throw badCredentials(e);
-			}
-			else {
+			} else {
 				throw LdapUtils.convertLdapException(e);
 			}
 		}
@@ -313,6 +317,12 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
 		return (BadCredentialsException) badCredentials().initCause(cause);
 	}
 
+	private InternalAuthenticationServiceException badLdapConnection(Throwable cause) {
+		return new InternalAuthenticationServiceException(messages.getMessage(
+				"LdapAuthenticationProvider.badLdapConnection",
+				"Connection to LDAP server failed."), cause);
+	}
+
 	private DirContextOperations searchForUser(DirContext context, String username)
 			throws NamingException {
 		SearchControls searchControls = new SearchControls();
@@ -327,6 +337,9 @@ public final class ActiveDirectoryLdapAuthenticationProvider extends
 					searchControls, searchRoot, searchFilter,
 					new Object[] { bindPrincipal, username });
 		}
+		catch (CommunicationException ldapCommunicationException) {
+			throw badLdapConnection(ldapCommunicationException);
+		}
 		catch (IncorrectResultSizeDataAccessException incorrectResults) {
 			// Search should never return multiple results if properly configured - just
 			// rethrow
diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
index 934b507d05..7302da3821 100644
--- a/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
+++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/ad/ActiveDirectoryLdapAuthenticationProviderTests.java
@@ -32,6 +32,7 @@ import org.springframework.security.authentication.AccountExpiredException;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.CredentialsExpiredException;
 import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
 import org.springframework.security.authentication.LockedException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
@@ -58,6 +59,9 @@ import static org.springframework.security.ldap.authentication.ad.ActiveDirector
  * @author Rob Winch
  */
 public class ActiveDirectoryLdapAuthenticationProviderTests {
+	public static final String EXISTING_LDAP_PROVIDER = "ldap://192.168.1.200/";
+	public static final String NON_EXISTING_LDAP_PROVIDER = "ldap://192.168.1.201/";
+
 	@Rule
 	public ExpectedException thrown = ExpectedException.none();
 
@@ -378,16 +382,29 @@ public class ActiveDirectoryLdapAuthenticationProviderTests {
 	}
 
 	@Test(expected = org.springframework.ldap.CommunicationException.class)
-	public void nonAuthenticationExceptionIsConvertedToSpringLdapException() {
-		provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
-				msg));
-		provider.authenticate(joe);
+	public void nonAuthenticationExceptionIsConvertedToSpringLdapException() throws Throwable {
+		try {
+			provider.contextFactory = createContextFactoryThrowing(new CommunicationException(
+					msg));
+			provider.authenticate(joe);
+		} catch (InternalAuthenticationServiceException e) {
+			// Since GH-8418 ldap communication exception is wrapped into InternalAuthenticationServiceException.
+			// This test is about the wrapped exception, so we throw it.
+			throw e.getCause();
+		}
+	}
+
+	@Test(expected = org.springframework.security.authentication.InternalAuthenticationServiceException.class )
+	public void connectionExceptionIsWrappedInInternalException() throws Exception {
+		ActiveDirectoryLdapAuthenticationProvider noneReachableProvider = new ActiveDirectoryLdapAuthenticationProvider(
+				"mydomain.eu", NON_EXISTING_LDAP_PROVIDER, "dc=ad,dc=eu,dc=mydomain");
+		noneReachableProvider.doAuthentication(joe);
 	}
 
 	@Test
 	public void rootDnProvidedSeparatelyFromDomainAlsoWorks() throws Exception {
 		ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(
-				"mydomain.eu", "ldap://192.168.1.200/", "dc=ad,dc=eu,dc=mydomain");
+				"mydomain.eu", EXISTING_LDAP_PROVIDER, "dc=ad,dc=eu,dc=mydomain");
 		checkAuthentication("dc=ad,dc=eu,dc=mydomain", provider);
 
 	}
@@ -413,8 +430,11 @@ public class ActiveDirectoryLdapAuthenticationProviderTests {
 			provider.authenticate(joe);
 			fail("CommunicationException was expected with a root cause of ClassNotFoundException");
 		}
-		catch (org.springframework.ldap.CommunicationException expected) {
-			assertThat(expected.getRootCause()).isInstanceOf(ClassNotFoundException.class);
+		catch (InternalAuthenticationServiceException expected) {
+			assertThat(expected.getCause()).isInstanceOf(org.springframework.ldap.CommunicationException.class);
+			org.springframework.ldap.CommunicationException cause =
+					(org.springframework.ldap.CommunicationException) expected.getCause();
+			assertThat(cause.getRootCause()).isInstanceOf(ClassNotFoundException.class);
 		}
 	}
 

From c399185365ae676e2fc9de26c0908d26c9e1a6c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1cs?= 
Date: Thu, 23 Apr 2020 18:22:51 +0200
Subject: [PATCH 098/348] Add ROLE_INFRASTRUCTURE to infrastructure beans

Closes gh-8407
---
 .../configuration/ObjectPostProcessorConfiguration.java       | 4 ++++
 .../configuration/GlobalMethodSecurityConfiguration.java      | 2 ++
 .../configuration/Jsr250MetadataSourceConfiguration.java      | 4 ++++
 .../configuration/ReactiveMethodSecurityConfiguration.java    | 2 ++
 4 files changed, 12 insertions(+)

diff --git a/config/src/main/java/org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.java
index e169bed3a2..3c61557c4c 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/configuration/ObjectPostProcessorConfiguration.java
@@ -16,8 +16,10 @@
 package org.springframework.security.config.annotation.configuration;
 
 import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -34,9 +36,11 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
  * @since 3.2
  */
 @Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 public class ObjectPostProcessorConfiguration {
 
 	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 	public ObjectPostProcessor objectPostProcessor(
 			AutowireCapableBeanFactory beanFactory) {
 		return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java
index e18e23a27d..eb3207f823 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.java
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.SmartInitializingSingleton;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.*;
 import org.springframework.core.annotation.AnnotationAttributes;
 import org.springframework.core.annotation.AnnotationUtils;
@@ -80,6 +81,7 @@ import org.springframework.util.Assert;
  * @see EnableGlobalMethodSecurity
  */
 @Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 public class GlobalMethodSecurityConfiguration
 		implements ImportAware, SmartInitializingSingleton, BeanFactoryAware {
 	private static final Log logger = LogFactory
diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MetadataSourceConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MetadataSourceConfiguration.java
index bfb920d90d..5c98bf48fe 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MetadataSourceConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/Jsr250MetadataSourceConfiguration.java
@@ -15,14 +15,18 @@
  */
 package org.springframework.security.config.annotation.method.configuration;
 
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Role;
 import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource;
 
 @Configuration(proxyBeanMethods = false)
+@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 class Jsr250MetadataSourceConfiguration {
 
 	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 	public Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() {
 		return new Jsr250MethodSecurityMetadataSource();
 	}
diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfiguration.java
index 8c95784c98..b1ba9ae5d8 100644
--- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfiguration.java
+++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityConfiguration.java
@@ -54,6 +54,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
 	}
 
 	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 	public DelegatingMethodSecurityMetadataSource methodMetadataSource(MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
 		ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
 				methodSecurityExpressionHandler);
@@ -74,6 +75,7 @@ class ReactiveMethodSecurityConfiguration implements ImportAware {
 	}
 
 	@Bean
+	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
 	public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
 		DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
 		if (this.grantedAuthorityDefaults != null) {

From e9e736d1e6e41ddd482f5dbce04746edce7dfa59 Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Thu, 30 Apr 2020 10:44:52 -0400
Subject: [PATCH 099/348] Clean up Javadoc

Fixes gh-8480
---
 .../security/crypto/encrypt/Encryptors.java           | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
index aee376b702..7ebfb5a356 100644
--- a/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
+++ b/crypto/src/main/java/org/springframework/security/crypto/encrypt/Encryptors.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2011-2016 the original author or authors.
+ * Copyright 2011-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.
@@ -32,7 +32,7 @@ public class Encryptors {
 	 * (Password-Based Key Derivation Function #2). Salts the password to prevent
 	 * dictionary attacks against the key. The provided salt is expected to be
 	 * hex-encoded; it should be random and at least 8 bytes in length. Also applies a
-	 * random 16 byte initialization vector to ensure each encrypted message will be
+	 * random 16-byte initialization vector to ensure each encrypted message will be
 	 * unique. Requires Java 6.
 	 *
 	 * @param password the password used to generate the encryptor's secret key; should
@@ -50,7 +50,7 @@ public class Encryptors {
 	 * Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation
 	 * Function #2). Salts the password to prevent dictionary attacks against the key. The
 	 * provided salt is expected to be hex-encoded; it should be random and at least 8
-	 * bytes in length. Also applies a random 16 byte initialization vector to ensure each
+	 * bytes in length. Also applies a random 16-byte initialization vector to ensure each
 	 * encrypted message will be unique. Requires Java 6.
 	 * NOTE: This mode is not
 	 * authenticated
@@ -63,7 +63,7 @@ public class Encryptors {
 	 * @param salt a hex-encoded, random, site-global salt value to use to generate the
 	 * key
 	 *
-	 * @see #stronger(CharSequence, CharSequence) which uses the significatly more secure
+	 * @see #stronger(CharSequence, CharSequence), which uses the significatly more secure
 	 * GCM (instead of CBC)
 	 */
 	public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
@@ -105,7 +105,10 @@ public class Encryptors {
 	 * not be shared
 	 * @param salt a hex-encoded, random, site-global salt value to use to generate the
 	 * secret key
+	 * @deprecated This encryptor is not secure. Instead, look to your data store for a
+	 * mechanism to query encrypted data.
 	 */
+	@Deprecated
 	public static TextEncryptor queryableText(CharSequence password, CharSequence salt) {
 		return new HexEncodingTextEncryptor(new AesBytesEncryptor(password.toString(),
 				salt));

From 94fd7e3a2ba6fa0e7678960e88fdb5d3229e6b4f Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:41:43 -0400
Subject: [PATCH 100/348] Update to GAE 1.9.80

Closes gh-8467
---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index a9a9095b38..25653d697f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
 aspectjVersion=1.9.5
-gaeVersion=1.9.79
+gaeVersion=1.9.80
 springBootVersion=2.2.6.RELEASE
 version=5.2.4.BUILD-SNAPSHOT
 org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError

From 4fa67a1d9bc996e6bb43524eb6dd82e6ff697cb1 Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:43:00 -0400
Subject: [PATCH 101/348] Update to Reactor Dysprosium-SR7

Closes gh-8464
---
 gradle/dependency-management.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index 17e8e0b783..8ef5f080f8 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -1,5 +1,5 @@
 if (!project.hasProperty('reactorVersion')) {
-	ext.reactorVersion = 'Dysprosium-SR6'
+	ext.reactorVersion = 'Dysprosium-SR7'
 }
 
 if (!project.hasProperty('springVersion')) {

From 19a4386e49205b0de3ed50cd8e2d811997e25efd Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:43:43 -0400
Subject: [PATCH 102/348] Update to Spring Framework 5.2.6.RELEASE

Closes gh-8463
---
 gradle/dependency-management.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index 8ef5f080f8..ecc8235680 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) {
 }
 
 if (!project.hasProperty('springVersion')) {
-	ext.springVersion = '5.2.5.RELEASE'
+	ext.springVersion = '5.2.6.RELEASE'
 }
 
 if (!project.hasProperty('springDataVersion')) {

From 98922b001a46285c38ef10098266bc6eff6687af Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:44:33 -0400
Subject: [PATCH 103/348] Update to Spring Data Moore-SR7

Closes gh-8462
---
 gradle/dependency-management.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index ecc8235680..ebc06e5808 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) {
 }
 
 if (!project.hasProperty('springDataVersion')) {
-	ext.springDataVersion = 'Moore-SR6'
+	ext.springDataVersion = 'Moore-SR7'
 }
 
 ext.rsocketVersion = '1.0.0-RC6'

From ba78fae2465504be81cff09a384ef4cfcc9bc2cb Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:45:37 -0400
Subject: [PATCH 104/348] Update RSocket to 1.0.0-RC7

Closes gh-8468
---
 gradle/dependency-management.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index ebc06e5808..1c2bdbb001 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) {
 	ext.springDataVersion = 'Moore-SR7'
 }
 
-ext.rsocketVersion = '1.0.0-RC6'
+ext.rsocketVersion = '1.0.0-RC7'
 
 dependencyManagement {
 	imports {

From d34f7a8474c7e75a2c50a1b9b54cfa8ba8ee0e84 Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:46:12 -0400
Subject: [PATCH 105/348] Update to org.powermock 2.0.7

Closes gh-8465
---
 gradle/dependency-management.gradle | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index 1c2bdbb001..a3fa246c15 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -25,11 +25,11 @@ dependencyManagement {
 		dependency 'org.gebish:geb-spock:0.10.0'
 		dependency 'org.jasig.cas:cas-server-webapp:4.2.7'
 		dependency 'org.powermock:powermock-api-mockito2:2.0.6'
-		dependency 'org.powermock:powermock-api-support:2.0.6'
-		dependency 'org.powermock:powermock-core:2.0.6'
-		dependency 'org.powermock:powermock-module-junit4-common:2.0.6'
-		dependency 'org.powermock:powermock-module-junit4:2.0.6'
-		dependency 'org.powermock:powermock-reflect:2.0.6'
+		dependency 'org.powermock:powermock-api-support:2.0.7'
+		dependency 'org.powermock:powermock-core:2.0.7'
+		dependency 'org.powermock:powermock-module-junit4-common:2.0.7'
+		dependency 'org.powermock:powermock-module-junit4:2.0.7'
+		dependency 'org.powermock:powermock-reflect:2.0.7'
 		dependency 'org.python:jython:2.5.0'
 		dependency 'org.spockframework:spock-core:1.0-groovy-2.4'
 		dependency 'org.spockframework:spock-spring:1.0-groovy-2.4'

From 6ccc18fde29ec080a55213fb977ff2af94bfc79a Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:47:08 -0400
Subject: [PATCH 106/348] Update to Jackson 2.10.4

Closes gh-8466
---
 gradle/dependency-management.gradle | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index a3fa246c15..8f22bc5183 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -42,9 +42,9 @@ dependencyManagement {
 		dependency 'asm:asm:3.1'
 		dependency 'ch.qos.logback:logback-classic:1.2.3'
 		dependency 'ch.qos.logback:logback-core:1.2.3'
-		dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.3'
-		dependency 'com.fasterxml.jackson.core:jackson-core:2.10.3'
-		dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.3'
+		dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.4'
+		dependency 'com.fasterxml.jackson.core:jackson-core:2.10.4'
+		dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.4'
 		dependency 'com.fasterxml:classmate:1.3.4'
 		dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1'
 		dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion'

From 01cce49ee5a1adba7f5254538af6c2385b1cd56e Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:48:03 -0400
Subject: [PATCH 107/348] Upgrade to embedded Apache Tomcat 9.0.34

Closes gh-8469
---
 gradle/dependency-management.gradle | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index 8f22bc5183..e6964d901d 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -145,12 +145,12 @@ dependencyManagement {
 		dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5'
 		dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5'
 		dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5'
-		dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.33'
-		dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.33'
-		dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.33'
-		dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.33'
+		dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.34'
+		dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.34'
+		dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.34'
+		dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.34'
 		dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23'
-		dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.33'
+		dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.34'
 		dependency "org.aspectj:aspectjrt:$aspectjVersion"
 		dependency "org.aspectj:aspectjtools:$aspectjVersion"
 		dependency "org.aspectj:aspectjweaver:$aspectjVersion"

From f43d00fac6cdbcc8ecf683b10eb809ea555bf3b6 Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Tue, 5 May 2020 17:50:07 -0400
Subject: [PATCH 108/348] Update to Byte Buddy 1.9.16

Closes gh-8481
---
 gradle/dependency-management.gradle | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle
index e6964d901d..366b829c46 100644
--- a/gradle/dependency-management.gradle
+++ b/gradle/dependency-management.gradle
@@ -86,8 +86,8 @@ dependencyManagement {
 		dependency 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'
 		dependency 'junit:junit:4.12'
 		dependency 'ldapsdk:ldapsdk:4.1'
-		dependency 'net.bytebuddy:byte-buddy-agent:1.9.10'
-		dependency 'net.bytebuddy:byte-buddy:1.9.10'
+		dependency 'net.bytebuddy:byte-buddy-agent:1.9.16'
+		dependency 'net.bytebuddy:byte-buddy:1.9.16'
 		dependency 'net.jcip:jcip-annotations:1.0'
 		dependency 'net.minidev:accessors-smart:1.2'
 		dependency 'net.minidev:json-smart:2.3'

From 69b1bc62ffe7c1e1bbf508951efa1e0235954198 Mon Sep 17 00:00:00 2001
From: Josh Cummings 
Date: Tue, 31 Mar 2020 15:55:32 -0600
Subject: [PATCH 109/348] Polish OpenSamlAuthenticationProvider

- Use type-safe CriteriaSet
- Keep Assertion immutable

Closes gh-8471
---
 .../OpenSamlAuthenticationProvider.java       | 503 +++++++++---------
 1 file changed, 262 insertions(+), 241 deletions(-)

diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
index 900f3cd820..42353a20ca 100644
--- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
+++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/authentication/OpenSamlAuthenticationProvider.java
@@ -25,13 +25,31 @@ import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMap
 import org.springframework.security.saml2.Saml2Exception;
 import org.springframework.security.saml2.credentials.Saml2X509Credential;
 import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
 
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
-import org.opensaml.saml.common.SignableSAMLObject;
-import org.opensaml.saml.common.assertion.AssertionValidationException;
+import org.opensaml.core.criterion.EntityIdCriterion;
+
 import org.opensaml.saml.common.assertion.ValidationContext;
 import org.opensaml.saml.common.assertion.ValidationResult;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.criterion.ProtocolCriterion;
+import org.opensaml.saml.metadata.criteria.role.impl.EvaluableProtocolRoleDescriptorCriterion;
 import org.opensaml.saml.saml2.assertion.ConditionValidator;
 import org.opensaml.saml.saml2.assertion.SAML20AssertionValidator;
 import org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters;
@@ -51,40 +69,36 @@ import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
 import org.opensaml.security.credential.Credential;
 import org.opensaml.security.credential.CredentialResolver;
 import org.opensaml.security.credential.CredentialSupport;
+import org.opensaml.security.credential.UsageType;
+import org.opensaml.security.credential.criteria.impl.EvaluableEntityIDCredentialCriterion;
+import org.opensaml.security.credential.criteria.impl.EvaluableUsageCredentialCriterion;
 import org.opensaml.security.credential.impl.CollectionCredentialResolver;
+import org.opensaml.security.criteria.UsageCriterion;
+import org.opensaml.security.x509.BasicX509Credential;
 import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
 import org.opensaml.xmlsec.encryption.support.DecryptionException;
 import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
 import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
-import org.opensaml.xmlsec.signature.support.SignatureException;
 import org.opensaml.xmlsec.signature.support.SignaturePrevalidator;
 import org.opensaml.xmlsec.signature.support.SignatureTrustEngine;
-import org.opensaml.xmlsec.signature.support.SignatureValidator;
 import org.opensaml.xmlsec.signature.support.impl.ExplicitKeySignatureTrustEngine;
 
-import java.security.cert.X509Certificate;
-import java.time.Duration;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static java.lang.String.format;
 import static java.util.Collections.singleton;
 import static java.util.Collections.singletonList;
+import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.CLOCK_SKEW;
+import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.COND_VALID_AUDIENCES;
+import static org.opensaml.saml.saml2.assertion.SAML2AssertionValidationParameters.SIGNATURE_REQUIRED;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.DECRYPTION_ERROR;
+import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR;
+import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_ASSERTION;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_DESTINATION;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_ISSUER;
+import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.INVALID_SIGNATURE;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.MALFORMED_RESPONSE_DATA;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.SUBJECT_NOT_FOUND;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.UNKNOWN_RESPONSE_CLASS;
 import static org.springframework.security.saml2.provider.service.authentication.Saml2ErrorCodes.USERNAME_NOT_FOUND;
 import static org.springframework.util.Assert.notNull;
-import static org.springframework.util.StringUtils.hasText;
 
 /**
  * Implementation of {@link AuthenticationProvider} for SAML authentications when receiving a
@@ -126,6 +140,20 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 
 	private static Log logger = LogFactory.getLog(OpenSamlAuthenticationProvider.class);
 
+	private final List conditions = Collections.singletonList(new AudienceRestrictionConditionValidator());
+	private final SubjectConfirmationValidator subjectConfirmationValidator = new BearerSubjectConfirmationValidator() {
+		@Nonnull
+		@Override
+		protected ValidationResult validateAddress(@Nonnull SubjectConfirmation confirmation,
+				@Nonnull Assertion assertion, @Nonnull ValidationContext context) {
+			// skipping address validation - gh-7514
+			return ValidationResult.VALID;
+		}
+	};
+	private final List subjects = Collections.singletonList(this.subjectConfirmationValidator);
+	private final List statements = Collections.emptyList();
+	private final SignaturePrevalidator signaturePrevalidator = new SAMLSignatureProfileValidator();
+
 	private final OpenSamlImplementation saml = OpenSamlImplementation.getInstance();
 	private Converter> authoritiesExtractor =
 			(a -> singletonList(new SimpleGrantedAuthority("ROLE_USER")));
@@ -174,17 +202,17 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 		try {
 			Saml2AuthenticationToken token = (Saml2AuthenticationToken) authentication;
-			Response samlResponse = getSaml2Response(token);
-			Assertion assertion = validateSaml2Response(token, token.getRecipientUri(), samlResponse);
+			Response response = parse(token.getSaml2Response());
+			List validAssertions = validateResponse(token, response);
+			Assertion assertion = validAssertions.get(0);
 			String username = getUsername(token, assertion);
 			return new Saml2Authentication(
 					new SimpleSaml2AuthenticatedPrincipal(username), token.getSaml2Response(),
-					this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion))
-			);
+					this.authoritiesMapper.mapAuthorities(getAssertionAuthorities(assertion)));
 		} catch (Saml2AuthenticationException e) {
 			throw e;
 		} catch (Exception e) {
-			throw authException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
+			throw authException(INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
 		}
 	}
 
@@ -200,167 +228,9 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 		return this.authoritiesExtractor.convert(assertion);
 	}
 
-	private String getUsername(Saml2AuthenticationToken token, Assertion assertion) throws Saml2AuthenticationException {
-		String username = null;
-		Subject subject = assertion.getSubject();
-		if (subject == null) {
-			throw authException(SUBJECT_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a subject");
-		}
-		if (subject.getNameID() != null) {
-			username = subject.getNameID().getValue();
-		}
-		else if (subject.getEncryptedID() != null) {
-			NameID nameId = decrypt(token, subject.getEncryptedID());
-			username = nameId.getValue();
-		}
-		if (username == null) {
-			throw authException(USERNAME_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a user identifier");
-		}
-		return username;
-	}
-
-	private Assertion validateSaml2Response(Saml2AuthenticationToken token,
-											String recipient,
-											Response samlResponse) throws Saml2AuthenticationException {
-		//optional validation if the response contains a destination
-		if (hasText(samlResponse.getDestination()) && !recipient.equals(samlResponse.getDestination())) {
-			throw authException(INVALID_DESTINATION, "Invalid SAML response destination: " + samlResponse.getDestination());
-		}
-
-		String issuer = samlResponse.getIssuer().getValue();
-		if (logger.isDebugEnabled()) {
-			logger.debug("Validating SAML response from " + issuer);
-		}
-		if (!hasText(issuer) || (!issuer.equals(token.getIdpEntityId()))) {
-			String message = String.format("Response issuer '%s' doesn't match '%s'", issuer, token.getIdpEntityId());
-			throw authException(INVALID_ISSUER, message);
-		}
-		Saml2AuthenticationException lastValidationError = null;
-
-		boolean responseSigned = hasValidSignature(samlResponse, token);
-		for (Assertion a : samlResponse.getAssertions()) {
-			if (logger.isDebugEnabled()) {
-				logger.debug("Checking plain assertion validity " + a);
-			}
-			try {
-				validateAssertion(recipient, a, token, !responseSigned);
-				return a;
-			} catch (Saml2AuthenticationException e) {
-				lastValidationError = e;
-			}
-		}
-		for (EncryptedAssertion ea : samlResponse.getEncryptedAssertions()) {
-			if (logger.isDebugEnabled()) {
-				logger.debug("Checking encrypted assertion validity " + ea);
-			}
-			try {
-				Assertion a = decrypt(token, ea);
-				validateAssertion(recipient, a, token, !responseSigned);
-				return a;
-			} catch (Saml2AuthenticationException e) {
-				lastValidationError = e;
-			}
-		}
-		if (lastValidationError != null) {
-			throw lastValidationError;
-		}
-		else {
-			throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response.");
-		}
-	}
-
-	private boolean hasValidSignature(SignableSAMLObject samlObject, Saml2AuthenticationToken token) {
-		if (!samlObject.isSigned()) {
-			if (logger.isDebugEnabled()) {
-				logger.debug("SAML object is not signed, no signatures found");
-			}
-			return false;
-		}
-
-		List verificationKeys = getVerificationCertificates(token);
-		if (verificationKeys.isEmpty()) {
-			return false;
-		}
-
-		for (X509Certificate certificate : verificationKeys) {
-			Credential credential = getVerificationCredential(certificate);
-			try {
-				SignatureValidator.validate(samlObject.getSignature(), credential);
-				if (logger.isDebugEnabled()) {
-					logger.debug("Valid signature found in SAML object:"+samlObject.getClass().getName());
-				}
-				return true;
-			}
-			catch (SignatureException ignored) {
-				if (logger.isTraceEnabled()) {
-					logger.trace("Signature validation failed with cert:"+certificate.toString(), ignored);
-				}
-				else if (logger.isDebugEnabled()) {
-					logger.debug("Signature validation failed with cert:"+certificate.toString());
-				}
-			}
-		}
-		return false;
-	}
-
-	private void validateAssertion(String recipient, Assertion a, Saml2AuthenticationToken token, boolean signatureRequired) {
-		SAML20AssertionValidator validator = getAssertionValidator(token);
-		Map validationParams = new HashMap<>();
-		validationParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false);
-		validationParams.put(
-				SAML2AssertionValidationParameters.CLOCK_SKEW,
-				this.responseTimeValidationSkew.toMillis()
-		);
-		validationParams.put(
-				SAML2AssertionValidationParameters.COND_VALID_AUDIENCES,
-				singleton(token.getLocalSpEntityId())
-		);
-		if (hasText(recipient)) {
-			validationParams.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, singleton(recipient));
-		}
-
-		if (signatureRequired && !hasValidSignature(a, token)) {
-			if (logger.isDebugEnabled()) {
-				logger.debug(format("Assertion [%s] does not a valid signature.", a.getID()));
-			}
-			throw authException(Saml2ErrorCodes.INVALID_SIGNATURE, "Assertion doesn't have a valid signature.");
-		}
-		//ensure that OpenSAML doesn't attempt signature validation, already performed
-		a.setSignature(null);
-
-		//ensure that we don't validate IP addresses as part of our validation gh-7514
-		if (a.getSubject() != null) {
-			for (SubjectConfirmation sc : a.getSubject().getSubjectConfirmations()) {
-				if (sc.getSubjectConfirmationData() != null) {
-					sc.getSubjectConfirmationData().setAddress(null);
-				}
-			}
-		}
-
-		//remainder of assertion validation
-		ValidationContext vctx = new ValidationContext(validationParams);
+	private Response parse(String response) throws Saml2Exception, Saml2AuthenticationException {
 		try {
-			ValidationResult result = validator.validate(a, vctx);
-			boolean valid = result.equals(ValidationResult.VALID);
-			if (!valid) {
-				if (logger.isDebugEnabled()) {
-					logger.debug(format("Failed to validate assertion from %s", token.getIdpEntityId()));
-				}
-				throw authException(Saml2ErrorCodes.INVALID_ASSERTION, vctx.getValidationFailureMessage());
-			}
-		}
-		catch (AssertionValidationException e) {
-			if (logger.isDebugEnabled()) {
-				logger.debug("Failed to validate assertion:", e);
-			}
-			throw authException(Saml2ErrorCodes.INTERNAL_VALIDATION_ERROR, e.getMessage(), e);
-		}
-
-	}
-
-	private Response getSaml2Response(Saml2AuthenticationToken token) throws Saml2Exception, Saml2AuthenticationException {
-		try {
-			Object result = this.saml.resolve(token.getSaml2Response());
+			Object result = this.saml.resolve(response);
 			if (result instanceof Response) {
 				return (Response) result;
 			}
@@ -373,68 +243,172 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 
 	}
 
-	private Saml2Error validationError(String code, String description) {
-		return new Saml2Error(
-				code,
-				description
-		);
+	private List validateResponse(Saml2AuthenticationToken token, Response response)
+			throws Saml2AuthenticationException {
+
+		List validAssertions = new ArrayList<>();
+		String issuer = response.getIssuer().getValue();
+		if (logger.isDebugEnabled()) {
+			logger.debug("Validating SAML response from " + issuer);
+		}
+
+		List assertions = new ArrayList<>(response.getAssertions());
+		for (EncryptedAssertion encryptedAssertion : response.getEncryptedAssertions()) {
+			Assertion assertion = decrypt(token, encryptedAssertion);
+			assertions.add(assertion);
+		}
+		if (assertions.isEmpty()) {
+			throw authException(MALFORMED_RESPONSE_DATA, "No assertions found in response.");
+		}
+
+		if (!isSigned(response, assertions)) {
+			throw authException(INVALID_SIGNATURE, "Either the response or one of the assertions is unsigned. " +
+					"Please either sign the response or all of the assertions.");
+		}
+
+		SignatureTrustEngine signatureTrustEngine = buildSignatureTrustEngine(token);
+
+		Map validationExceptions = new HashMap<>();
+		if (response.isSigned()) {
+			SAMLSignatureProfileValidator profileValidator = new SAMLSignatureProfileValidator();
+			try {
+				profileValidator.validate(response.getSignature());
+			} catch (Exception e) {
+				validationExceptions.put(INVALID_SIGNATURE, authException(INVALID_SIGNATURE,
+						"Invalid signature for SAML Response [" + response.getID() + "]", e));
+			}
+
+			try {
+				CriteriaSet criteriaSet = new CriteriaSet();
+				criteriaSet.add(new EvaluableEntityIDCredentialCriterion(new EntityIdCriterion(issuer)));
+				criteriaSet.add(new EvaluableProtocolRoleDescriptorCriterion(new ProtocolCriterion(SAMLConstants.SAML20P_NS)));
+				criteriaSet.add(new EvaluableUsageCredentialCriterion(new UsageCriterion(UsageType.SIGNING)));
+				if (!signatureTrustEngine.validate(response.getSignature(), criteriaSet)) {
+					validationExceptions.put(INVALID_SIGNATURE, authException(INVALID_SIGNATURE,
+							"Invalid signature for SAML Response [" + response.getID() + "]"));
+				}
+			} catch (Exception e) {
+				validationExceptions.put(INVALID_SIGNATURE, authException(INVALID_SIGNATURE,
+						"Invalid signature for SAML Response [" + response.getID() + "]", e));
+			}
+		}
+
+		String destination = response.getDestination();
+		if (StringUtils.hasText(destination) && !destination.equals(token.getRecipientUri())) {
+			String message = "Invalid destination [" + destination + "] for SAML response [" + response.getID() + "]";
+			validationExceptions.put(INVALID_DESTINATION, authException(INVALID_DESTINATION, message));
+		}
+
+		if (!StringUtils.hasText(issuer) || !issuer.equals(token.getIdpEntityId())) {
+			String message = String.format("Invalid issuer [%s] for SAML response [%s]", issuer, response.getID());
+			validationExceptions.put(INVALID_ISSUER, authException(INVALID_ISSUER, message));
+		}
+
+		SAML20AssertionValidator validator = buildSamlAssertionValidator(signatureTrustEngine);
+		ValidationContext context = buildValidationContext(token, response);
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Validating " + assertions.size() + " assertions");
+		}
+		for (Assertion assertion : assertions) {
+			if (logger.isTraceEnabled()) {
+				logger.trace("Validating assertion " + assertion.getID());
+			}
+			try {
+				validAssertions.add(validateAssertion(assertion, validator, context));
+			} catch (Exception e) {
+				String message = String.format("Invalid assertion [%s] for SAML response [%s]", assertion.getID(), response.getID());
+				validationExceptions.put(INVALID_ASSERTION, authException(INVALID_ASSERTION, message, e));
+			}
+		}
+
+		if (validationExceptions.isEmpty()) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Successfully validated SAML Response [" + response.getID() + "]");
+			}
+		} else {
+			if (logger.isTraceEnabled()) {
+				logger.debug("Found " + validationExceptions.size() + " validation errors in SAML response [" + response.getID() + "]: " +
+						validationExceptions.values());
+			} else if (logger.isDebugEnabled()) {
+				logger.debug("Found " + validationExceptions.size() + " validation errors in SAML response [" + response.getID() + "]");
+			}
+		}
+
+		if (!validationExceptions.isEmpty()) {
+			throw validationExceptions.values().iterator().next();
+		}
+		if (validAssertions.isEmpty()) {
+			throw authException(MALFORMED_RESPONSE_DATA, "No valid assertions found in response.");
+		}
+
+		return validAssertions;
 	}
 
-	private Saml2AuthenticationException authException(String code, String description) throws Saml2AuthenticationException {
-		return new Saml2AuthenticationException(
-				validationError(code, description)
-		);
+	private boolean isSigned(Response samlResponse, List assertions) {
+		if (samlResponse.isSigned()) {
+			return true;
+		}
+
+		for (Assertion assertion : assertions) {
+			if (!assertion.isSigned()) {
+				return false;
+			}
+		}
+
+		return true;
 	}
 
-
-	private Saml2AuthenticationException authException(String code, String description, Exception cause) throws Saml2AuthenticationException {
-		return new Saml2AuthenticationException(
-				validationError(code, description),
-				cause
-		);
-	}
-
-	private SAML20AssertionValidator getAssertionValidator(Saml2AuthenticationToken provider) {
-		List conditions = Collections.singletonList(new AudienceRestrictionConditionValidator());
-		BearerSubjectConfirmationValidator subjectConfirmationValidator = new BearerSubjectConfirmationValidator();
-
-		List subjects = Collections.singletonList(subjectConfirmationValidator);
-		List statements = Collections.emptyList();
-
+	private SignatureTrustEngine buildSignatureTrustEngine(Saml2AuthenticationToken token) {
 		Set credentials = new HashSet<>();
-		for (X509Certificate key : getVerificationCertificates(provider)) {
-			Credential cred = getVerificationCredential(key);
+		for (X509Certificate key : getVerificationCertificates(token)) {
+			BasicX509Credential cred = new BasicX509Credential(key);
+			cred.setUsageType(UsageType.SIGNING);
+			cred.setEntityId(token.getIdpEntityId());
 			credentials.add(cred);
 		}
 		CredentialResolver credentialsResolver = new CollectionCredentialResolver(credentials);
-		SignatureTrustEngine signatureTrustEngine = new ExplicitKeySignatureTrustEngine(
+		return new ExplicitKeySignatureTrustEngine(
 				credentialsResolver,
 				DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver()
 		);
-		SignaturePrevalidator signaturePrevalidator = new SAMLSignatureProfileValidator();
+	}
+
+	private ValidationContext buildValidationContext(Saml2AuthenticationToken token, Response response) {
+		Map validationParams = new HashMap<>();
+		validationParams.put(SIGNATURE_REQUIRED, !response.isSigned());
+		validationParams.put(CLOCK_SKEW, this.responseTimeValidationSkew.toMillis());
+		validationParams.put(COND_VALID_AUDIENCES, singleton(token.getLocalSpEntityId()));
+		if (StringUtils.hasText(token.getRecipientUri())) {
+			validationParams.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, singleton(token.getRecipientUri()));
+		}
+		return new ValidationContext(validationParams);
+	}
+
+	private SAML20AssertionValidator buildSamlAssertionValidator(SignatureTrustEngine signatureTrustEngine) {
 		return new SAML20AssertionValidator(
-				conditions,
-				subjects,
-				statements,
-				signatureTrustEngine,
-				signaturePrevalidator
-		);
+				this.conditions, this.subjects, this.statements, signatureTrustEngine, this.signaturePrevalidator);
 	}
 
-	private Credential getVerificationCredential(X509Certificate certificate) {
-		return CredentialSupport.getSimpleCredential(certificate, null);
-	}
+	private Assertion validateAssertion(Assertion assertion,
+			SAML20AssertionValidator validator, ValidationContext context) {
 
-	private Decrypter getDecrypter(Saml2X509Credential key) {
-		Credential credential = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
-		KeyInfoCredentialResolver resolver = new StaticKeyInfoCredentialResolver(credential);
-		Decrypter decrypter = new Decrypter(null, resolver, this.saml.getEncryptedKeyResolver());
-		decrypter.setRootInNewDocument(true);
-		return decrypter;
+		ValidationResult result;
+		try {
+			result = validator.validate(assertion, context);
+		} catch (Exception e) {
+			throw new Saml2Exception("An error occurred while validation the assertion", e);
+		}
+		if (result != ValidationResult.VALID) {
+			throw new Saml2Exception("An error occurred while validating the assertion: " +
+					context.getValidationFailureMessage());
+		}
+		return assertion;
 	}
 
 	private Assertion decrypt(Saml2AuthenticationToken token, EncryptedAssertion assertion)
 			throws Saml2AuthenticationException {
+
 		Saml2AuthenticationException last = null;
 		List decryptionCredentials = getDecryptionCredentials(token);
 		if (decryptionCredentials.isEmpty()) {
@@ -452,22 +426,12 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 		throw last;
 	}
 
-	private NameID decrypt(Saml2AuthenticationToken token, EncryptedID assertion) throws Saml2AuthenticationException {
-		Saml2AuthenticationException last = null;
-		List decryptionCredentials = getDecryptionCredentials(token);
-		if (decryptionCredentials.isEmpty()) {
-			throw authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
-		}
-		for (Saml2X509Credential key : decryptionCredentials) {
-			Decrypter decrypter = getDecrypter(key);
-			try {
-				return (NameID) decrypter.decrypt(assertion);
-			}
-			catch (DecryptionException e) {
-				last = authException(DECRYPTION_ERROR, e.getMessage(), e);
-			}
-		}
-		throw last;
+	private Decrypter getDecrypter(Saml2X509Credential key) {
+		Credential credential = CredentialSupport.getSimpleCredential(key.getCertificate(), key.getPrivateKey());
+		KeyInfoCredentialResolver resolver = new StaticKeyInfoCredentialResolver(credential);
+		Decrypter decrypter = new Decrypter(null, resolver, this.saml.getEncryptedKeyResolver());
+		decrypter.setRootInNewDocument(true);
+		return decrypter;
 	}
 
 	private List getDecryptionCredentials(Saml2AuthenticationToken token) {
@@ -489,4 +453,61 @@ public final class OpenSamlAuthenticationProvider implements AuthenticationProvi
 		}
 		return result;
 	}
+
+	private String getUsername(Saml2AuthenticationToken token, Assertion assertion)
+			throws Saml2AuthenticationException {
+
+		String username = null;
+		Subject subject = assertion.getSubject();
+		if (subject == null) {
+			throw authException(SUBJECT_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a subject");
+		}
+		if (subject.getNameID() != null) {
+			username = subject.getNameID().getValue();
+		}
+		else if (subject.getEncryptedID() != null) {
+			NameID nameId = decrypt(token, subject.getEncryptedID());
+			username = nameId.getValue();
+		}
+		if (username == null) {
+			throw authException(USERNAME_NOT_FOUND, "Assertion [" + assertion.getID() + "] is missing a user identifier");
+		}
+		return username;
+	}
+
+	private NameID decrypt(Saml2AuthenticationToken token, EncryptedID assertion)
+			throws Saml2AuthenticationException {
+
+		Saml2AuthenticationException last = null;
+		List decryptionCredentials = getDecryptionCredentials(token);
+		if (decryptionCredentials.isEmpty()) {
+			throw authException(DECRYPTION_ERROR, "No valid decryption credentials found.");
+		}
+		for (Saml2X509Credential key : decryptionCredentials) {
+			Decrypter decrypter = getDecrypter(key);
+			try {
+				return (NameID) decrypter.decrypt(assertion);
+			}
+			catch (DecryptionException e) {
+				last = authException(DECRYPTION_ERROR, e.getMessage(), e);
+			}
+		}
+		throw last;
+	}
+
+	private Saml2Error validationError(String code, String description) {
+		return new Saml2Error(code, description);
+	}
+
+	private Saml2AuthenticationException authException(String code, String description)
+			throws Saml2AuthenticationException {
+
+		return new Saml2AuthenticationException(validationError(code, description));
+	}
+
+	private Saml2AuthenticationException authException(String code, String description, Exception cause)
+			throws Saml2AuthenticationException {
+
+		return new Saml2AuthenticationException(validationError(code, description), cause);
+	}
 }

From 3ed31400f761fd6c0bc726b293117506bb87d3eb Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Wed, 6 May 2020 11:33:30 -0400
Subject: [PATCH 110/348] Update SAML2 errors in integration tests

---
 .../security/samples/Saml2LoginIntegrationTests.java | 12 +++++-------
 1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java
index 1f40142987..a401ce8a4f 100644
--- a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java
+++ b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java
@@ -77,7 +77,6 @@ import javax.servlet.http.HttpSession;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.startsWith;
 import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildConditions;
 import static org.springframework.security.samples.OpenSamlActionTestingSupport.buildIssuer;
@@ -237,8 +236,8 @@ public class Saml2LoginIntegrationTests {
 		sendResponse(response, "/login?error")
 				.andExpect(
 						saml2AuthenticationExceptionMatcher(
-								"invalid_signature",
-								equalTo("Assertion doesn't have a valid signature.")
+								"invalid_assertion",
+								containsString("Invalid assertion [assertion] for SAML response")
 						)
 				);
 	}
@@ -253,7 +252,7 @@ public class Saml2LoginIntegrationTests {
 				.andExpect(
 						saml2AuthenticationExceptionMatcher(
 								"invalid_assertion",
-								containsString("Assertion 'assertion' with NotOnOrAfter condition of")
+								containsString("Invalid assertion [assertion] for SAML response")
 						)
 				);
 	}
@@ -268,7 +267,7 @@ public class Saml2LoginIntegrationTests {
 				.andExpect(
 						saml2AuthenticationExceptionMatcher(
 								"invalid_assertion",
-								containsString("Assertion 'assertion' with NotBefore condition of")
+								containsString("Invalid assertion [assertion] for SAML response")
 						)
 				);
 	}
@@ -285,8 +284,7 @@ public class Saml2LoginIntegrationTests {
 						saml2AuthenticationExceptionMatcher(
 								"invalid_issuer",
 								containsString(
-										"Response issuer 'invalid issuer' doesn't match "+
-												"'https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php'"
+										"Invalid issuer [invalid issuer] for SAML response"
 								)
 						)
 				);

From 50da82d88d7749ae859e932d7558985e68007e9b Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Wed, 6 May 2020 12:55:50 -0400
Subject: [PATCH 111/348] Temporarily build against Framework 5.2.x snapshot

Issue: gh-8489
---
 Jenkinsfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Jenkinsfile b/Jenkinsfile
index 6c20f09529..681a6f31eb 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -56,7 +56,7 @@ try {
 				sh "git clean -dfx"
 				try {
 					withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) {
-						sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
+						sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace"
 					}
 				} catch(Exception e) {
 					currentBuild.result = 'FAILED: snapshots'

From b7212bd97510840b116c8a1ddf180cab7509d90c Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Wed, 6 May 2020 15:16:32 -0400
Subject: [PATCH 112/348] Release 5.2.4.RELEASE

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 25653d697f..543ab2a578 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
 aspectjVersion=1.9.5
 gaeVersion=1.9.80
 springBootVersion=2.2.6.RELEASE
-version=5.2.4.BUILD-SNAPSHOT
+version=5.2.4.RELEASE
 org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError

From f9872d3dee70031cba03be069d581463599c2196 Mon Sep 17 00:00:00 2001
From: Eleftheria Stein 
Date: Wed, 6 May 2020 15:59:24 -0400
Subject: [PATCH 113/348] Next Development Version

---
 gradle.properties | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/gradle.properties b/gradle.properties
index 543ab2a578..13b6f42ebb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,5 @@
 aspectjVersion=1.9.5
 gaeVersion=1.9.80
 springBootVersion=2.2.6.RELEASE
-version=5.2.4.RELEASE
+version=5.2.5.BUILD-SNAPSHOT
 org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError

From bd936165672fe1f72391f8769d7dd651e57d0bc0 Mon Sep 17 00:00:00 2001
From: Rob Winch 
Date: Mon, 11 May 2020 17:20:27 -0500
Subject: [PATCH 114/348] Fix non-standard HTTP method for CsrfWebFilter

Closes gh-8452
---
 .../security/web/server/csrf/CsrfWebFilter.java  |  5 +++--
 .../web/server/csrf/CsrfWebFilterTests.java      | 16 +++++++++++++++-
 2 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java
index 33d06d39da..64dcc1b6af 100644
--- a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java
+++ b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2017 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.
@@ -60,6 +60,7 @@ import static java.lang.Boolean.TRUE;
  * 

* * @author Rob Winch + * @author Parikshit Dutta * @since 5.0 */ public class CsrfWebFilter implements WebFilter { @@ -187,7 +188,7 @@ public class CsrfWebFilter implements WebFilter { @Override public Mono matches(ServerWebExchange exchange) { return Mono.just(exchange.getRequest()) - .map(r -> r.getMethod()) + .flatMap(r -> Mono.justOrEmpty(r.getMethod())) .filter(m -> ALLOWED_METHODS.contains(m)) .flatMap(m -> MatchResult.notMatch()) .switchIfEmpty(MatchResult.match()); diff --git a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java index cd3df76693..94d0334e18 100644 --- a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 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. @@ -20,11 +20,14 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; + +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -45,6 +48,7 @@ import static org.springframework.web.reactive.function.BodyInserters.fromMultip /** * @author Rob Winch + * @author Parikshit Dutta * @since 5.0 */ @RunWith(MockitoJUnitRunner.class) @@ -183,6 +187,16 @@ public class CsrfWebFilterTests { chainResult.assertWasSubscribed(); } + @Test + // gh-8452 + public void matchesRequireCsrfProtectionWhenNonStandardHTTPMethodIsUsed() { + HttpMethod customHttpMethod = HttpMethod.resolve("non-standard-http-method"); + MockServerWebExchange nonStandardHttpRequest = from(MockServerHttpRequest.method(customHttpMethod, "/")); + + ServerWebExchangeMatcher serverWebExchangeMatcher = CsrfWebFilter.DEFAULT_CSRF_MATCHER; + assertThat(serverWebExchangeMatcher.matches(nonStandardHttpRequest).map(MatchResult::isMatch).block()).isTrue(); + } + @Test public void doFilterWhenSkipExchangeInvokedThenSkips() { PublisherProbe chainResult = PublisherProbe.empty(); From ce7c501f9c9d3b4e0e3f0a14a03bc00a58532f35 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 12 May 2020 13:07:24 -0500 Subject: [PATCH 115/348] AbstractUserDetailsReactiveAuthenticationManager uses boundidElastic() Some JVMs have blocking operations when accessing SecureRandom and thus this needs to be performed in a pool that is larger than the number of CPUs Closes gh-7522 --- .../AbstractUserDetailsReactiveAuthenticationManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java b/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java index d32f7e6e20..cbc6e3b71b 100644 --- a/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java +++ b/core/src/main/java/org/springframework/security/authentication/AbstractUserDetailsReactiveAuthenticationManager.java @@ -55,7 +55,7 @@ public abstract class AbstractUserDetailsReactiveAuthenticationManager implement private ReactiveUserDetailsPasswordService userDetailsPasswordService; - private Scheduler scheduler = Schedulers.newParallel("password-encoder", Schedulers.DEFAULT_POOL_SIZE, true); + private Scheduler scheduler = Schedulers.boundedElastic(); private UserDetailsChecker preAuthenticationChecks = user -> { if (!user.isAccountNonLocked()) { From e382c269efaf992b252c243d9b06f03c01c91dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1cs?= Date: Sat, 9 May 2020 22:12:42 +0200 Subject: [PATCH 116/348] Document NoOpPasswordEncoder will not be removed This commit adds extension to deprecation notice. Fixes gh-8506 --- .../security/crypto/password/NoOpPasswordEncoder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java index 907eb7c718..3c28e16741 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/NoOpPasswordEncoder.java @@ -26,7 +26,8 @@ package org.springframework.security.crypto.password; * @deprecated This PasswordEncoder is not secure. Instead use an * adaptive one way function like BCryptPasswordEncoder, Pbkdf2PasswordEncoder, or * SCryptPasswordEncoder. Even better use {@link DelegatingPasswordEncoder} which supports - * password upgrades. + * password upgrades. There are no plans to remove this support. It is deprecated to indicate that + * this is a legacy implementation and using it is considered insecure. */ @Deprecated public final class NoOpPasswordEncoder implements PasswordEncoder { From 9f33ce312ab97d91c5cd33886606de82e9cb999a Mon Sep 17 00:00:00 2001 From: Artyom Tarynin Date: Mon, 11 May 2020 20:13:43 +0300 Subject: [PATCH 117/348] Update AntPathRequestMatcher.java Fixes gh-8512 --- .../security/web/util/matcher/AntPathRequestMatcher.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java index 1acaac55cc..146398c0c2 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/AntPathRequestMatcher.java @@ -68,7 +68,7 @@ public final class AntPathRequestMatcher /** * Creates a matcher with the specific pattern which will match all HTTP methods in a - * case insensitive manner. + * case sensitive manner. * * @param pattern the ant pattern to use for matching */ @@ -77,7 +77,7 @@ public final class AntPathRequestMatcher } /** - * Creates a matcher with the supplied pattern and HTTP method in a case insensitive + * Creates a matcher with the supplied pattern and HTTP method in a case sensitive * manner. * * @param pattern the ant pattern to use for matching From 21c1d98f648bddea715e0485f58294ca295d36ed Mon Sep 17 00:00:00 2001 From: cbornet Date: Fri, 15 May 2020 15:46:39 +0200 Subject: [PATCH 118/348] Create the CSRF token on the bounded elactic scheduler The CSRF token is created with a call to UUID.randomUUID which is blocking. This change ensures this blocking call is done on the bounded elastic scheduler which supports blocking calls. Fixes gh-8128 --- .../web/server/csrf/WebSessionServerCsrfTokenRepository.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java index 8680da5863..e09512c0e2 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/WebSessionServerCsrfTokenRepository.java @@ -18,6 +18,7 @@ package org.springframework.security.web.server.csrf; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -48,7 +49,9 @@ public class WebSessionServerCsrfTokenRepository @Override public Mono generateToken(ServerWebExchange exchange) { - return Mono.fromCallable(() -> createCsrfToken()); + return Mono.just(exchange) + .publishOn(Schedulers.boundedElastic()) + .fromCallable(() -> createCsrfToken()); } @Override From eaaee899fcd367d42128c8cb1b3a4129e8b8ac74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1cs?= Date: Sat, 16 May 2020 14:33:53 +0200 Subject: [PATCH 119/348] Object ID Identicy conversion to long fails on old schema This change fixed a bug which tried to convert non-string object as string Fixes gh-7621 --- .../security/acls/jdbc/AclClassIdUtils.java | 2 +- .../security/acls/jdbc/AclClassIdUtilsTest.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java b/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java index 677dbb5cb8..65b5d80f54 100644 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java +++ b/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java @@ -118,7 +118,7 @@ class AclClassIdUtils { */ private Long convertToLong(Serializable identifier) { Long idAsLong; - if (canConvertFromStringTo(Long.class)) { + if (conversionService.canConvert(identifier.getClass(), Long.class)) { idAsLong = conversionService.convert(identifier, Long.class); } else { idAsLong = Long.valueOf(identifier.toString()); diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTest.java b/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTest.java index 65e7fd83fc..5dd2e36f31 100644 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTest.java +++ b/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTest.java @@ -24,6 +24,7 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.convert.ConversionService; import java.io.Serializable; +import java.math.BigInteger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.UUID; @@ -39,6 +40,7 @@ import static org.mockito.BDDMockito.given; public class AclClassIdUtilsTest { private static final Long DEFAULT_IDENTIFIER = 999L; + private static final BigInteger BIGINT_IDENTIFIER = new BigInteger("999"); private static final String DEFAULT_IDENTIFIER_AS_STRING = DEFAULT_IDENTIFIER.toString(); @Mock @@ -62,6 +64,15 @@ public class AclClassIdUtilsTest { assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); } + @Test + public void shouldReturnLongIfIdentifierIsBigInteger() throws SQLException { + // when + Serializable newIdentifier = aclClassIdUtils.identifierFrom(BIGINT_IDENTIFIER, resultSet); + + // then + assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); + } + @Test public void shouldReturnLongIfClassIdTypeIsNull() throws SQLException { // given From 279ddbe2239fe60694cb2a9465df544feabc906c Mon Sep 17 00:00:00 2001 From: Maksim Vinogradov Date: Sat, 27 Apr 2019 08:32:34 +0300 Subject: [PATCH 120/348] Prevent StackOverflowError for AccessControlEntryImpl.hashCode Getting StackOverflowError when invoke AclImpl.hashCode because of cross-references between AclImpl and AccessControlEntryImpl Remove from AccessControlEntryImpl.hashCode method invocation of acl.hashCode fixes gh-5401 --- .../acls/domain/AccessControlEntryImpl.java | 7 +++---- .../security/acls/domain/AclImplTests.java | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java index f39dbd30b5..fb41f101d6 100644 --- a/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java +++ b/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited + * Copyright 2002-2016 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. @@ -131,10 +131,9 @@ public class AccessControlEntryImpl implements AccessControlEntry, @Override public int hashCode() { - int result = this.acl.hashCode(); - result = 31 * result + this.permission.hashCode(); + int result = this.permission.hashCode(); result = 31 * result + (this.id != null ? this.id.hashCode() : 0); - result = 31 * result + this.sid.hashCode(); + result = 31 * result + (this.sid.hashCode()); result = 31 * result + (this.auditFailure ? 1 : 0); result = 31 * result + (this.auditSuccess ? 1 : 0); result = 31 * result + (this.granting ? 1 : 0); diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java index f91d42a1bd..7313048d20 100644 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java +++ b/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java @@ -577,6 +577,25 @@ public class AclImplTests { assertThat(acl.isGranted(permissions, sids, false)).isTrue(); } + @Test + public void hashCodeWithoutStackOverFlow() throws Exception { + //given + Sid sid = new PrincipalSid("pSid"); + ObjectIdentity oid = new ObjectIdentityImpl("type", 1); + AclAuthorizationStrategy authStrategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("role")); + PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()); + + AclImpl acl = new AclImpl(oid, 1L, authStrategy, grantingStrategy, null, null, false, sid); + AccessControlEntryImpl ace = new AccessControlEntryImpl(1L, acl, sid, BasePermission.READ, true, true, true); + + Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); + fieldAces.setAccessible(true); + List aces = (List) fieldAces.get(acl); + aces.add(ace); + //when - then none StackOverFlowError been raised + ace.hashCode(); + } + // ~ Inner Classes // ================================================================================================== From 774ea6980b130ea428ce47b969df087f0d92e4aa Mon Sep 17 00:00:00 2001 From: justmehyp <1435791775@qq.com> Date: Tue, 19 May 2020 12:34:36 +0800 Subject: [PATCH 121/348] Remove unused field 'digester' in Md4PasswordEncoder `private Digester digester;` defined in Md4PasswordEncoder is never used. So remove it. Closes gh-8553 --- .../security/crypto/password/Md4PasswordEncoder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java b/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java index a0110e8612..7fe06a979d 100644 --- a/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java +++ b/crypto/src/main/java/org/springframework/security/crypto/password/Md4PasswordEncoder.java @@ -83,8 +83,6 @@ public class Md4PasswordEncoder implements PasswordEncoder { private StringKeyGenerator saltGenerator = new Base64StringKeyGenerator(); private boolean encodeHashAsBase64; - private Digester digester; - public void setEncodeHashAsBase64(boolean encodeHashAsBase64) { this.encodeHashAsBase64 = encodeHashAsBase64; From a6dd119266d68f71d820e5024a2057bb8d17745b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 26 May 2020 10:16:56 -0400 Subject: [PATCH 122/348] Mock request with non-standard HTTP method in test Fixes gh-8594 --- .../security/web/server/csrf/CsrfWebFilterTests.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java index 94d0334e18..800f4806f7 100644 --- a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java @@ -21,9 +21,9 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; @@ -190,11 +190,12 @@ public class CsrfWebFilterTests { @Test // gh-8452 public void matchesRequireCsrfProtectionWhenNonStandardHTTPMethodIsUsed() { - HttpMethod customHttpMethod = HttpMethod.resolve("non-standard-http-method"); - MockServerWebExchange nonStandardHttpRequest = from(MockServerHttpRequest.method(customHttpMethod, "/")); + ServerHttpRequest nonStandardHttpRequest = mock(ServerHttpRequest.class); + ServerWebExchange nonStandardHttpExchange = mock(ServerWebExchange.class); + when(nonStandardHttpExchange.getRequest()).thenReturn(nonStandardHttpRequest); ServerWebExchangeMatcher serverWebExchangeMatcher = CsrfWebFilter.DEFAULT_CSRF_MATCHER; - assertThat(serverWebExchangeMatcher.matches(nonStandardHttpRequest).map(MatchResult::isMatch).block()).isTrue(); + assertThat(serverWebExchangeMatcher.matches(nonStandardHttpExchange).map(MatchResult::isMatch).block()).isTrue(); } @Test From 24fd9579c5ca8d610b6acf1c669cda83f332f0de Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 29 May 2020 16:49:01 -0500 Subject: [PATCH 123/348] Delay AuthenticationPrincipalArgumentResolver Creation Use ObjectProvider to delay its lookup. Closes gh-8613 --- .../ServerHttpSecurityConfiguration.java | 5 +++-- .../reactive/EnableWebFluxSecurityTests.java | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java index a6c7f4783f..b7d4b3e3c7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.web.reactive; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -87,11 +88,11 @@ class ServerHttpSecurityConfiguration { @Bean public WebFluxConfigurer authenticationPrincipalArgumentResolverConfigurer( - AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver) { + ObjectProvider authenticationPrincipalArgumentResolver) { return new WebFluxConfigurer() { @Override public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { - configurer.addCustomResolver(authenticationPrincipalArgumentResolver); + configurer.addCustomResolver(authenticationPrincipalArgumentResolver.getObject()); } }; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java index e317d8f625..4be1c2b325 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java @@ -47,6 +47,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; +import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver; import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.WebFilterChainProxy; @@ -59,6 +60,7 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.result.view.AbstractView; @@ -434,4 +436,23 @@ public class EnableWebFluxSecurityTests { Child() { } } + + @Test + // gh-8596 + public void resolveAuthenticationPrincipalArgumentResolverFirstDoesNotCauseBeanCurrentlyInCreationException() { + this.spring.register(EnableWebFluxSecurityConfiguration.class, + ReactiveAuthenticationTestConfiguration.class, + DelegatingWebFluxConfiguration.class).autowire(); + } + + @EnableWebFluxSecurity + @Configuration(proxyBeanMethods = false) + static class EnableWebFluxSecurityConfiguration { + /** + * It is necessary to Autowire AuthenticationPrincipalArgumentResolver because it triggers eager loading of + * AuthenticationPrincipalArgumentResolver bean which causes BeanCurrentlyInCreationException + */ + @Autowired + AuthenticationPrincipalArgumentResolver resolver; + } } From 7e9c7534f579ad254b8d2ec91572f76b4d1c2326 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 2 Jun 2020 17:14:44 -0400 Subject: [PATCH 124/348] Update to Spring Boot 2.2.7 Fixes gh-8630 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 13b6f42ebb..fae80076a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.80 -springBootVersion=2.2.6.RELEASE +springBootVersion=2.2.7.RELEASE version=5.2.5.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From e87783024cc2051beb02f056df14a2ed15af341a Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 2 Jun 2020 17:15:05 -0400 Subject: [PATCH 125/348] Update to RSocket 1.0.0 Fixes gh-8626 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 366b829c46..c5f6358a19 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR7' } -ext.rsocketVersion = '1.0.0-RC7' +ext.rsocketVersion = '1.0.0' dependencyManagement { imports { From 1b93b0c9a66bb51edf581b8abe613974c32c0895 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 2 Jun 2020 17:17:05 -0400 Subject: [PATCH 126/348] Update to mockwebserver 3.14.9 Fixes gh-8627 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index c5f6358a19..f910b50292 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -20,7 +20,7 @@ dependencyManagement { } dependencies { dependency 'cglib:cglib-nodep:3.3.0' - dependency 'com.squareup.okhttp3:mockwebserver:3.14.7' + dependency 'com.squareup.okhttp3:mockwebserver:3.14.9' dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' From 05b9591e440f21e5e62fa0d8bcc9c1ad337902a4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 2 Jun 2020 17:17:29 -0400 Subject: [PATCH 127/348] Update to Jython 2.5.3 Fixes gh-8628 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f910b50292..e913c0583d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -30,7 +30,7 @@ dependencyManagement { dependency 'org.powermock:powermock-module-junit4-common:2.0.7' dependency 'org.powermock:powermock-module-junit4:2.0.7' dependency 'org.powermock:powermock-reflect:2.0.7' - dependency 'org.python:jython:2.5.0' + dependency 'org.python:jython:2.5.3' dependency 'org.spockframework:spock-core:1.0-groovy-2.4' dependency 'org.spockframework:spock-spring:1.0-groovy-2.4' } From d9919b838dcb48ba5aec90610b4bed9f60db2714 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 2 Jun 2020 17:17:56 -0400 Subject: [PATCH 128/348] Update to okhttp 3.14.9 Fixes gh-8629 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e913c0583d..79f20cb986 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -58,7 +58,7 @@ dependencyManagement { dependency 'com.nimbusds:lang-tag:1.4.3' dependency 'com.nimbusds:nimbus-jose-jwt:7.8.1' dependency 'com.nimbusds:oauth2-oidc-sdk:6.14' - dependency 'com.squareup.okhttp3:okhttp:3.14.7' + dependency 'com.squareup.okhttp3:okhttp:3.14.9' dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' dependency 'com.sun.xml.bind:jaxb-impl:2.3.2' From 04453cec967b072920832fd32c2afa35446155ca Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 2 Jun 2020 17:18:21 -0400 Subject: [PATCH 129/348] Update to groovy 2.4.19 Fixes gh-8525 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 79f20cb986..96bb643f5f 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -158,9 +158,9 @@ dependencyManagement { dependency 'org.attoparser:attoparser:2.0.4.RELEASE' dependency 'org.bouncycastle:bcpkix-jdk15on:1.64' dependency 'org.bouncycastle:bcprov-jdk15on:1.64' - dependency 'org.codehaus.groovy:groovy-all:2.4.17' - dependency 'org.codehaus.groovy:groovy-json:2.4.17' - dependency 'org.codehaus.groovy:groovy:2.4.17' + dependency 'org.codehaus.groovy:groovy-all:2.4.19' + dependency 'org.codehaus.groovy:groovy-json:2.4.19' + dependency 'org.codehaus.groovy:groovy:2.4.19' dependency 'org.eclipse.jdt:ecj:3.12.3' dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.27.v20200227' dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227' From b88e094f371c492f05d35aa86cf5088441a441f5 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 3 Jun 2020 10:59:04 -0400 Subject: [PATCH 130/348] Release 5.2.5.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index fae80076a0..2c0bc2da91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.80 springBootVersion=2.2.7.RELEASE -version=5.2.5.BUILD-SNAPSHOT +version=5.2.5.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From fd7fe482f5f9dbf5f3caeb16ab0c8dfb7f9473ca Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 3 Jun 2020 15:29:44 -0400 Subject: [PATCH 131/348] Revert "Release 5.2.5.RELEASE" This reverts commit b88e094f371c492f05d35aa86cf5088441a441f5. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2c0bc2da91..fae80076a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.80 springBootVersion=2.2.7.RELEASE -version=5.2.5.RELEASE +version=5.2.5.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From d5eeec0ae693c1fd31b9dd54058ecb72fef7e686 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 3 Jun 2020 15:29:59 -0400 Subject: [PATCH 132/348] Temporarily ignore RSocket integration tests Issue gh-8643 --- .../security/config/annotation/rsocket/HelloRSocketITests.java | 2 ++ .../rsocket/RSocketMessageHandlerConnectionITests.java | 2 ++ .../config/annotation/rsocket/RSocketMessageHandlerITests.java | 2 ++ .../java/sample/HelloRSocketApplicationITests.java | 2 ++ 4 files changed, 8 insertions(+) diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java index 876e22d2a5..2f06ce7bec 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java @@ -22,6 +22,7 @@ import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -52,6 +53,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; */ @ContextConfiguration @RunWith(SpringRunner.class) +@Ignore public class HelloRSocketITests { @Autowired RSocketMessageHandler handler; diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java index 269bcbb951..4ce7b75ff1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java @@ -22,6 +22,7 @@ import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -58,6 +59,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; */ @ContextConfiguration @RunWith(SpringRunner.class) +@Ignore public class RSocketMessageHandlerConnectionITests { @Autowired RSocketMessageHandler handler; diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java index 19f5e1010c..d9886c3e79 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java @@ -23,6 +23,7 @@ import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -58,6 +59,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; */ @ContextConfiguration @RunWith(SpringRunner.class) +@Ignore public class RSocketMessageHandlerITests { @Autowired RSocketMessageHandler handler; diff --git a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java b/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java index 420588c356..912c1be300 100644 --- a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java +++ b/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java @@ -15,6 +15,7 @@ */ package sample; +import org.junit.Ignore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.rsocket.context.LocalRSocketServerPort; import org.springframework.boot.test.context.SpringBootTest; @@ -40,6 +41,7 @@ import static org.springframework.security.rsocket.metadata.UsernamePasswordMeta @RunWith(SpringRunner.class) @TestPropertySource(properties = "spring.rsocket.server.port=0") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Ignore public class HelloRSocketApplicationITests { @Autowired From 38a731d5e484de063f369115f75eea133ac6e68b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 3 Jun 2020 16:02:37 -0400 Subject: [PATCH 133/348] Release 5.2.5.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index fae80076a0..2c0bc2da91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.80 springBootVersion=2.2.7.RELEASE -version=5.2.5.BUILD-SNAPSHOT +version=5.2.5.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 69ff2ab3fcac705182ec8febfb24a474aaa5b68e Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 3 Jun 2020 16:17:30 -0400 Subject: [PATCH 134/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 2c0bc2da91..bc1c8a7aea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.80 springBootVersion=2.2.7.RELEASE -version=5.2.5.RELEASE +version=5.2.6.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From c71a893e08db75cb3939ff2a99bddae4669185e4 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Fri, 5 Jun 2020 12:20:53 -0500 Subject: [PATCH 135/348] Add subscriberContext to PayloadSocketAcceptor delegate.accept Closes gh-8654 --- .../rsocket/core/PayloadSocketAcceptor.java | 1 + .../CaptureSecurityContextSocketAcceptor.java | 50 +++++++++++++++++++ .../core/PayloadSocketAcceptorTests.java | 39 ++++++++++++--- 3 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 rsocket/src/test/java/org/springframework/security/rsocket/core/CaptureSecurityContextSocketAcceptor.java diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java index 5e8839c139..a5a849096b 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadSocketAcceptor.java @@ -72,6 +72,7 @@ class PayloadSocketAcceptor implements SocketAcceptor { return intercept(setup, dataMimeType, metadataMimeType) .flatMap(ctx -> this.delegate.accept(setup, sendingSocket) .map(acceptingSocket -> new PayloadInterceptorRSocket(acceptingSocket, this.interceptors, metadataMimeType, dataMimeType, ctx)) + .subscriberContext(ctx) ); } diff --git a/rsocket/src/test/java/org/springframework/security/rsocket/core/CaptureSecurityContextSocketAcceptor.java b/rsocket/src/test/java/org/springframework/security/rsocket/core/CaptureSecurityContextSocketAcceptor.java new file mode 100644 index 0000000000..f434ed4c70 --- /dev/null +++ b/rsocket/src/test/java/org/springframework/security/rsocket/core/CaptureSecurityContextSocketAcceptor.java @@ -0,0 +1,50 @@ +/* + * Copyright 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.rsocket.core; + +import io.rsocket.ConnectionSetupPayload; +import io.rsocket.RSocket; +import io.rsocket.SocketAcceptor; +import reactor.core.publisher.Mono; + +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; + +/** + * A {@link SocketAcceptor} that captures the {@link SecurityContext} and then continues with the {@link RSocket} + * @author Rob Winch + */ +class CaptureSecurityContextSocketAcceptor implements SocketAcceptor { + private final RSocket accept; + + private SecurityContext securityContext; + + CaptureSecurityContextSocketAcceptor(RSocket accept) { + this.accept = accept; + } + + @Override + public Mono accept(ConnectionSetupPayload setup, RSocket sendingSocket) { + return ReactiveSecurityContextHolder.getContext() + .doOnNext(securityContext -> this.securityContext = securityContext) + .thenReturn(this.accept); + } + + public SecurityContext getSecurityContext() { + return this.securityContext; + } +} diff --git a/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadSocketAcceptorTests.java b/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadSocketAcceptorTests.java index 943fc978b9..69b7e2356d 100644 --- a/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadSocketAcceptorTests.java +++ b/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadSocketAcceptorTests.java @@ -16,6 +16,10 @@ package org.springframework.security.rsocket.core; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import io.rsocket.ConnectionSetupPayload; import io.rsocket.Payload; import io.rsocket.RSocket; @@ -27,16 +31,16 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import reactor.core.publisher.Mono; +import reactor.util.context.Context; + import org.springframework.http.MediaType; +import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.rsocket.api.PayloadExchange; import org.springframework.security.rsocket.api.PayloadInterceptor; -import org.springframework.security.rsocket.core.PayloadInterceptorRSocket; -import org.springframework.security.rsocket.core.PayloadSocketAcceptor; -import reactor.core.publisher.Mono; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -144,6 +148,27 @@ public class PayloadSocketAcceptorTests { assertThat(exchange.getDataMimeType()).isEqualTo(MediaType.APPLICATION_JSON); } + + @Test + // gh-8654 + public void acceptWhenDelegateAcceptRequiresReactiveSecurityContext() { + when(this.setupPayload.metadataMimeType()).thenReturn(MediaType.TEXT_PLAIN_VALUE); + when(this.setupPayload.dataMimeType()).thenReturn(MediaType.APPLICATION_JSON_VALUE); + SecurityContext expectedSecurityContext = new SecurityContextImpl(new TestingAuthenticationToken("user", "password", "ROLE_USER")); + CaptureSecurityContextSocketAcceptor captureSecurityContext = new CaptureSecurityContextSocketAcceptor(this.rSocket); + PayloadInterceptor authenticateInterceptor = (exchange, chain) -> { + Context withSecurityContext = ReactiveSecurityContextHolder.withSecurityContext(Mono.just(expectedSecurityContext)); + return chain.next(exchange) + .subscriberContext(withSecurityContext); + }; + List interceptors = Arrays.asList(authenticateInterceptor); + this.acceptor = new PayloadSocketAcceptor(captureSecurityContext, interceptors); + + this.acceptor.accept(this.setupPayload, this.rSocket).block(); + + assertThat(captureSecurityContext.getSecurityContext()).isEqualTo(expectedSecurityContext); + } + private PayloadExchange captureExchange() { when(this.delegate.accept(any(), any())).thenReturn(Mono.just(this.rSocket)); when(this.interceptor.intercept(any(), any())).thenReturn(Mono.empty()); From 0e37c722e2febe031b94c22a87a65f69cba0554c Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 8 Jun 2020 16:14:34 -0400 Subject: [PATCH 136/348] Revert "Temporarily ignore RSocket integration tests" This reverts commit d5eeec0ae693c1fd31b9dd54058ecb72fef7e686. Fixes gh-8643 --- .../security/config/annotation/rsocket/HelloRSocketITests.java | 2 -- .../rsocket/RSocketMessageHandlerConnectionITests.java | 2 -- .../config/annotation/rsocket/RSocketMessageHandlerITests.java | 2 -- .../java/sample/HelloRSocketApplicationITests.java | 2 -- 4 files changed, 8 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java index 2f06ce7bec..876e22d2a5 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/HelloRSocketITests.java @@ -22,7 +22,6 @@ import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -53,7 +52,6 @@ import static org.assertj.core.api.Assertions.assertThatCode; */ @ContextConfiguration @RunWith(SpringRunner.class) -@Ignore public class HelloRSocketITests { @Autowired RSocketMessageHandler handler; diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java index 4ce7b75ff1..269bcbb951 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerConnectionITests.java @@ -22,7 +22,6 @@ import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +58,6 @@ import static org.assertj.core.api.Assertions.assertThatCode; */ @ContextConfiguration @RunWith(SpringRunner.class) -@Ignore public class RSocketMessageHandlerConnectionITests { @Autowired RSocketMessageHandler handler; diff --git a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java index d9886c3e79..19f5e1010c 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/rsocket/RSocketMessageHandlerITests.java @@ -23,7 +23,6 @@ import io.rsocket.transport.netty.server.CloseableChannel; import io.rsocket.transport.netty.server.TcpServerTransport; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +58,6 @@ import static org.assertj.core.api.Assertions.assertThatCode; */ @ContextConfiguration @RunWith(SpringRunner.class) -@Ignore public class RSocketMessageHandlerITests { @Autowired RSocketMessageHandler handler; diff --git a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java b/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java index 912c1be300..420588c356 100644 --- a/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java +++ b/samples/boot/hellorsocket/src/integration-test/java/sample/HelloRSocketApplicationITests.java @@ -15,7 +15,6 @@ */ package sample; -import org.junit.Ignore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.rsocket.context.LocalRSocketServerPort; import org.springframework.boot.test.context.SpringBootTest; @@ -41,7 +40,6 @@ import static org.springframework.security.rsocket.metadata.UsernamePasswordMeta @RunWith(SpringRunner.class) @TestPropertySource(properties = "spring.rsocket.server.port=0") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@Ignore public class HelloRSocketApplicationITests { @Autowired From 98467755adc6dfb168e00d67f7acb7cf25d5615f Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 8 Jun 2020 17:18:33 -0400 Subject: [PATCH 137/348] Update to RSocket 1.0.1 Fixes gh-8664 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 96bb643f5f..abb33be08f 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR7' } -ext.rsocketVersion = '1.0.0' +ext.rsocketVersion = '1.0.1' dependencyManagement { imports { From 11c1236261bc492d56e6a95b2fa8ba3a1ad028a2 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 9 Jun 2020 14:40:39 -0400 Subject: [PATCH 138/348] OAuth2AuthorizationCodeGrantWebFilter should handle OAuth2AuthorizationException Fixes gh-8609 --- ...thorizationCodeAuthenticationProvider.java | 19 +++++- ...tionCodeReactiveAuthenticationManager.java | 21 +++++-- .../OAuth2AuthorizationExchangeValidator.java | 47 --------------- ...OAuth2AuthorizationCodeGrantWebFilter.java | 16 +++-- ...ationCodeAuthenticationTokenConverter.java | 3 +- ...2AuthorizationCodeGrantWebFilterTests.java | 59 +++++++++++++++++++ 6 files changed, 106 insertions(+), 59 deletions(-) delete mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java index fc5fb598b3..4ad62b09ce 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java @@ -20,7 +20,11 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; +import org.springframework.security.oauth2.core.OAuth2AuthorizationException; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.util.Assert; /** @@ -40,6 +44,7 @@ import org.springframework.util.Assert; * @see Section 4.1.4 Access Token Response */ public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider { + private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; private final OAuth2AccessTokenResponseClient accessTokenResponseClient; /** @@ -59,8 +64,18 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication; - OAuth2AuthorizationExchangeValidator.validate( - authorizationCodeAuthentication.getAuthorizationExchange()); + OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication + .getAuthorizationExchange().getAuthorizationResponse(); + if (authorizationResponse.statusError()) { + throw new OAuth2AuthorizationException(authorizationResponse.getError()); + } + + OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication + .getAuthorizationExchange().getAuthorizationRequest(); + if (!authorizationResponse.getState().equals(authorizationRequest.getState())) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + throw new OAuth2AuthorizationException(oauth2Error); + } OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse( diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java index 4ecdd3b85c..28e4d7bdfe 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.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. @@ -22,9 +22,13 @@ import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessT import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthorizationException; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2RefreshToken; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.util.Assert; import reactor.core.publisher.Mono; @@ -55,8 +59,8 @@ import java.util.function.Function; * @see Section 4.1.3 Access Token Request * @see Section 4.1.4 Access Token Response */ -public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements - ReactiveAuthenticationManager { +public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager { + private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; private final ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient; public OAuth2AuthorizationCodeReactiveAuthenticationManager( @@ -70,7 +74,16 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements return Mono.defer(() -> { OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication; - OAuth2AuthorizationExchangeValidator.validate(token.getAuthorizationExchange()); + OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange().getAuthorizationResponse(); + if (authorizationResponse.statusError()) { + return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError())); + } + + OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange().getAuthorizationRequest(); + if (!authorizationResponse.getState().equals(authorizationRequest.getState())) { + OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); + return Mono.error(new OAuth2AuthorizationException(oauth2Error)); + } OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest( token.getClientRegistration(), diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java deleted file mode 100644 index a240e0521a..0000000000 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2002-2019 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.client.authentication; - -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; - -/** - * A validator for an "exchange" of an OAuth 2.0 Authorization Request and Response. - * - * @author Joe Grandja - * @since 5.1 - * @see OAuth2AuthorizationExchange - */ -final class OAuth2AuthorizationExchangeValidator { - private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter"; - - static void validate(OAuth2AuthorizationExchange authorizationExchange) { - OAuth2AuthorizationRequest authorizationRequest = authorizationExchange.getAuthorizationRequest(); - OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse(); - - if (authorizationResponse.statusError()) { - throw new OAuth2AuthorizationException(authorizationResponse.getError()); - } - - if (!authorizationResponse.getState().equals(authorizationRequest.getState())) { - OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); - throw new OAuth2AuthorizationException(oauth2Error); - } - } -} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java index 7ef55667cb..e010d191c2 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java @@ -27,6 +27,8 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; @@ -173,15 +175,21 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter { public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { return this.requiresAuthenticationMatcher.matches(exchange) .filter(ServerWebExchangeMatcher.MatchResult::isMatch) - .flatMap(matchResult -> this.authenticationConverter.convert(exchange)) + .flatMap(matchResult -> + this.authenticationConverter.convert(exchange) + .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException( + e.getError(), e.getError().toString()))) .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) - .flatMap(token -> authenticate(exchange, chain, token)); + .flatMap(token -> authenticate(exchange, chain, token)) + .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler + .onAuthenticationFailure(new WebFilterExchange(exchange, chain), e)); } - private Mono authenticate(ServerWebExchange exchange, - WebFilterChain chain, Authentication token) { + private Mono authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) { WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain); return this.authenticationManager.authenticate(token) + .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException( + e.getError(), e.getError().toString())) .switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass())))) .flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange)) .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java index 2e9bd68c17..6ff9d34fd1 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java @@ -18,7 +18,6 @@ package org.springframework.security.oauth2.client.web.server; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -33,7 +32,7 @@ import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; /** - * Converts from a {@link ServerWebExchange} to an {@link OAuth2LoginAuthenticationToken} that can be authenticated. The + * Converts from a {@link ServerWebExchange} to an {@link OAuth2AuthorizationCodeAuthenticationToken} that can be authenticated. The * converter does not validate any errors it only performs a conversion. * @author Rob Winch * @since 5.1 diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java index db8f1b80c1..2ca60e7bb0 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java @@ -29,6 +29,9 @@ import org.springframework.security.oauth2.client.authentication.TestOAuth2Autho import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.TestClientRegistrations; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthorizationException; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.util.CollectionUtils; @@ -41,6 +44,7 @@ import java.util.LinkedHashMap; import java.util.Map; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -234,6 +238,61 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests { verifyZeroInteractions(this.authenticationManager); } + // gh-8609 + @Test + public void filterWhenAuthenticationConverterThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() { + ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); + when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(Mono.empty()); + + MockServerHttpRequest authorizationRequest = + createAuthorizationRequest("/authorization/callback"); + OAuth2AuthorizationRequest oauth2AuthorizationRequest = + createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration); + when(this.authorizationRequestRepository.loadAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + when(this.authorizationRequestRepository.removeAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + + MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse); + DefaultWebFilterChain chain = new DefaultWebFilterChain( + e -> e.getResponse().setComplete(), Collections.emptyList()); + + assertThatThrownBy(() -> this.filter.filter(exchange, chain).block()) + .isInstanceOf(OAuth2AuthenticationException.class) + .hasMessageContaining("client_registration_not_found"); + verifyZeroInteractions(this.authenticationManager); + } + + // gh-8609 + @Test + public void filterWhenAuthenticationManagerThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() { + ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); + when(this.clientRegistrationRepository.findByRegistrationId(any())) + .thenReturn(Mono.just(clientRegistration)); + + MockServerHttpRequest authorizationRequest = + createAuthorizationRequest("/authorization/callback"); + OAuth2AuthorizationRequest oauth2AuthorizationRequest = + createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration); + when(this.authorizationRequestRepository.loadAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + when(this.authorizationRequestRepository.removeAuthorizationRequest(any())) + .thenReturn(Mono.just(oauth2AuthorizationRequest)); + + when(this.authenticationManager.authenticate(any())) + .thenReturn(Mono.error(new OAuth2AuthorizationException(new OAuth2Error("authorization_error")))); + + MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest); + MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse); + DefaultWebFilterChain chain = new DefaultWebFilterChain( + e -> e.getResponse().setComplete(), Collections.emptyList()); + + assertThatThrownBy(() -> this.filter.filter(exchange, chain).block()) + .isInstanceOf(OAuth2AuthenticationException.class) + .hasMessageContaining("authorization_error"); + } + private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest( MockServerHttpRequest authorizationRequest, ClientRegistration registration) { Map attributes = new HashMap<>(); From 674e2c0a8e72886b69bd95683d4749eff0301b07 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 9 Jun 2020 14:40:56 -0400 Subject: [PATCH 139/348] OAuth2LoginAuthenticationWebFilter should handle OAuth2AuthorizationException Issue gh-8609 --- .../config/web/server/ServerHttpSecurity.java | 10 ++++++-- .../config/web/server/OAuth2LoginTests.java | 24 +++++++++++++++++-- ...tionCodeReactiveAuthenticationManager.java | 13 +++++----- .../AuthenticationWebFilter.java | 15 +++++------- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index d60654c066..df7d68597a 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -33,6 +33,8 @@ import java.util.function.Supplier; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import reactor.core.publisher.Mono; import reactor.util.context.Context; @@ -1089,8 +1091,12 @@ public class ServerHttpSecurity { private ServerAuthenticationConverter getAuthenticationConverter(ReactiveClientRegistrationRepository clientRegistrationRepository) { if (this.authenticationConverter == null) { - ServerOAuth2AuthorizationCodeAuthenticationTokenConverter authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository); - authenticationConverter.setAuthorizationRequestRepository(getAuthorizationRequestRepository()); + ServerOAuth2AuthorizationCodeAuthenticationTokenConverter delegate = + new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository); + delegate.setAuthorizationRequestRepository(getAuthorizationRequestRepository()); + ServerAuthenticationConverter authenticationConverter = exchange -> + delegate.convert(exchange).onErrorMap(OAuth2AuthorizationException.class, + e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString())); this.authenticationConverter = authenticationConverter; } return this.authenticationConverter; diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index 4723da1806..b1e5662a3e 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -103,7 +103,10 @@ import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.springframework.security.oauth2.jwt.TestJwts.jwt; /** @@ -683,7 +686,6 @@ public class OAuth2LoginTests { } } - @Test public void logoutWhenUsingOidcLogoutHandlerThenRedirects() { this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); @@ -739,6 +741,24 @@ public class OAuth2LoginTests { } } + // gh-8609 + @Test + public void oauth2LoginWhenAuthenticationConverterFailsThenDefaultRedirectToLogin() { + this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire(); + + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(this.springSecurity) + .build(); + + webTestClient.get() + .uri("/login/oauth2/code/google") + .exchange() + .expectStatus() + .is3xxRedirection() + .expectHeader() + .valueEquals("Location", "/login?error"); + } + static class GitHubWebFilter implements WebFilter { @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java index 9852ff1479..88550ace3e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -121,13 +121,14 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements .getAuthorizationExchange().getAuthorizationResponse(); if (authorizationResponse.statusError()) { - throw new OAuth2AuthenticationException( - authorizationResponse.getError(), authorizationResponse.getError().toString()); + return Mono.error(new OAuth2AuthenticationException( + authorizationResponse.getError(), authorizationResponse.getError().toString())); } if (!authorizationResponse.getState().equals(authorizationRequest.getState())) { OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE); - throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); + return Mono.error(new OAuth2AuthenticationException( + oauth2Error, oauth2Error.toString())); } OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest( @@ -139,7 +140,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements .onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString())) .onErrorMap(JwtException.class, e -> { OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, e.getMessage(), null); - throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), e); + return new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), e); }); }); } @@ -166,7 +167,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements INVALID_ID_TOKEN_ERROR_CODE, "Missing (required) ID Token in Token Response for Client Registration: " + clientRegistration.getRegistrationId(), null); - throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()); + return Mono.error(new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString())); } return createOidcToken(clientRegistration, accessTokenResponse) diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java b/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java index 330157ec76..a8fc5afa82 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -106,19 +106,16 @@ public class AuthenticationWebFilter implements WebFilter { .filter( matchResult -> matchResult.isMatch()) .flatMap( matchResult -> this.authenticationConverter.convert(exchange)) .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) - .flatMap( token -> authenticate(exchange, chain, token)); + .flatMap( token -> authenticate(exchange, chain, token)) + .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler + .onAuthenticationFailure(new WebFilterExchange(exchange, chain), e)); } - private Mono authenticate(ServerWebExchange exchange, - WebFilterChain chain, Authentication token) { - WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain); - + private Mono authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) { return this.authenticationManagerResolver.resolve(exchange.getRequest()) .flatMap(authenticationManager -> authenticationManager.authenticate(token)) .switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass())))) - .flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange)) - .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler - .onAuthenticationFailure(webFilterExchange, e)); + .flatMap(authentication -> onAuthenticationSuccess(authentication, new WebFilterExchange(exchange, chain))); } protected Mono onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) { From bff6d82dd0c7bac25124f4b1fc08d04b5de6bc19 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Thu, 4 Jun 2020 12:53:00 +0300 Subject: [PATCH 140/348] DefaultWebSecurityExpressionHandler uses RoleHierarchy bean Fixes gh-7059 --- .../annotation/web/builders/WebSecurity.java | 9 +++++- .../WebSecurityConfigurationTests.java | 30 ++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java index e08b1eae97..47776bacaa 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/WebSecurity.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. @@ -31,6 +31,7 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.http.HttpMethod; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.SecurityExpressionHandler; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.SecurityBuilder; @@ -74,6 +75,7 @@ import org.springframework.web.filter.DelegatingFilterProxy; * @see WebSecurityConfiguration * * @author Rob Winch + * @author Evgeniy Cheban * @since 3.2 */ public final class WebSecurity extends @@ -383,6 +385,11 @@ public final class WebSecurity extends throws BeansException { this.defaultWebSecurityExpressionHandler .setApplicationContext(applicationContext); + + try { + this.defaultWebSecurityExpressionHandler.setRoleHierarchy(applicationContext.getBean(RoleHierarchy.class)); + } catch (NoSuchBeanDefinitionException e) {} + try { this.defaultWebSecurityExpressionHandler.setPermissionEvaluator(applicationContext.getBean( PermissionEvaluator.class)); diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java index c27fca6201..c1d7d47b9f 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 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. @@ -32,6 +32,8 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.expression.AbstractSecurityExpressionHandler; import org.springframework.security.access.expression.SecurityExpressionHandler; +import org.springframework.security.access.hierarchicalroles.RoleHierarchy; +import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; @@ -69,6 +71,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * * @author Rob Winch * @author Joe Grandja + * @author Evgeniy Cheban */ public class WebSecurityConfigurationTests { @Rule @@ -290,6 +293,31 @@ public class WebSecurityConfigurationTests { } } + @Test + public void securityExpressionHandlerWhenRoleHierarchyBeanThenRoleHierarchyUsed() { + this.spring.register(WebSecurityExpressionHandlerRoleHierarchyBeanConfig.class).autowire(); + TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused", "ROLE_ADMIN"); + FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest("GET", ""), + new MockHttpServletResponse(), new MockFilterChain()); + + AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class); + EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation); + Expression expression = handler.getExpressionParser() + .parseExpression("hasRole('ROLE_USER')"); + boolean granted = expression.getValue(evaluationContext, Boolean.class); + assertThat(granted).isTrue(); + } + + @EnableWebSecurity + static class WebSecurityExpressionHandlerRoleHierarchyBeanConfig extends WebSecurityConfigurerAdapter { + @Bean + RoleHierarchy roleHierarchy() { + RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); + roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER"); + return roleHierarchy; + } + } + @Test public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() { this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire(); From fa9898dd6df87f581f7e6ee6d99704c3e0dd8554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1cs?= Date: Sat, 9 May 2020 21:53:33 +0200 Subject: [PATCH 141/348] formLogin() and login() implement Mergable This is necessary so that default requests like Spring REST Docs work. Closes gh-7572 --- .../SecurityMockMvcRequestBuilders.java | 77 ++++++++++++++++--- .../SecurityMockMvcRequestPostProcessors.java | 2 +- ...yMockMvcRequestBuildersFormLoginTests.java | 38 ++++++++- ...MockMvcRequestBuildersFormLogoutTests.java | 40 +++++++++- 4 files changed, 141 insertions(+), 16 deletions(-) diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuilders.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuilders.java index 451f76fcd5..fb5a69d24d 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuilders.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuilders.java @@ -15,16 +15,18 @@ */ package org.springframework.security.test.web.servlet.request; -import javax.servlet.ServletContext; - +import org.springframework.beans.Mergeable; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.web.csrf.CsrfToken; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; import org.springframework.web.util.UriComponentsBuilder; +import javax.servlet.ServletContext; + import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -86,15 +88,23 @@ public final class SecurityMockMvcRequestBuilders { * @author Rob Winch * @since 4.0 */ - public static final class LogoutRequestBuilder implements RequestBuilder { + public static final class LogoutRequestBuilder implements RequestBuilder, Mergeable { private String logoutUrl = "/logout"; private RequestPostProcessor postProcessor = csrf(); + private Mergeable parent; @Override public MockHttpServletRequest buildRequest(ServletContext servletContext) { - MockHttpServletRequest request = post(this.logoutUrl) - .accept(MediaType.TEXT_HTML, MediaType.ALL) - .buildRequest(servletContext); + MockHttpServletRequestBuilder logoutRequest = post(this.logoutUrl) + .accept(MediaType.TEXT_HTML, MediaType.ALL); + + if (this.parent != null) { + logoutRequest = (MockHttpServletRequestBuilder) logoutRequest.merge(this.parent); + } + + MockHttpServletRequest request = logoutRequest.buildRequest(servletContext); + logoutRequest.postProcessRequest(request); + return this.postProcessor.postProcessRequest(request); } @@ -122,6 +132,24 @@ public final class SecurityMockMvcRequestBuilders { return this; } + @Override + public boolean isMergeEnabled() { + return true; + } + + @Override + public Object merge(Object parent) { + if (parent == null) { + return this; + } + if (parent instanceof Mergeable) { + this.parent = (Mergeable) parent; + return this; + } else { + throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); + } + } + private LogoutRequestBuilder() { } } @@ -132,22 +160,31 @@ public final class SecurityMockMvcRequestBuilders { * @author Rob Winch * @since 4.0 */ - public static final class FormLoginRequestBuilder implements RequestBuilder { + public static final class FormLoginRequestBuilder implements RequestBuilder, Mergeable { private String usernameParam = "username"; private String passwordParam = "password"; private String username = "user"; private String password = "password"; private String loginProcessingUrl = "/login"; private MediaType acceptMediaType = MediaType.APPLICATION_FORM_URLENCODED; + private Mergeable parent; private RequestPostProcessor postProcessor = csrf(); @Override public MockHttpServletRequest buildRequest(ServletContext servletContext) { - MockHttpServletRequest request = post(this.loginProcessingUrl) - .accept(this.acceptMediaType).param(this.usernameParam, this.username) - .param(this.passwordParam, this.password) - .buildRequest(servletContext); + MockHttpServletRequestBuilder loginRequest = post(this.loginProcessingUrl) + .accept(this.acceptMediaType) + .param(this.usernameParam, this.username) + .param(this.passwordParam, this.password); + + if (this.parent != null) { + loginRequest = (MockHttpServletRequestBuilder) loginRequest.merge(this.parent); + } + + MockHttpServletRequest request = loginRequest.buildRequest(servletContext); + loginRequest.postProcessRequest(request); + return this.postProcessor.postProcessRequest(request); } @@ -258,6 +295,24 @@ public final class SecurityMockMvcRequestBuilders { return this; } + @Override + public boolean isMergeEnabled() { + return true; + } + + @Override + public Object merge(Object parent) { + if (parent == null) { + return this; + } + if (parent instanceof Mergeable ) { + this.parent = (Mergeable) parent; + return this; + } else { + throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); + } + } + private FormLoginRequestBuilder() { } } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index 017567930a..aeb55699b4 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -410,7 +410,7 @@ public final class SecurityMockMvcRequestPostProcessors { private final CsrfTokenRepository delegate; - private TestCsrfTokenRepository(CsrfTokenRepository delegate) { + TestCsrfTokenRepository(CsrfTokenRepository delegate) { this.delegate = delegate; } diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLoginTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLoginTests.java index 0a2501449a..3a00f6a080 100644 --- a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLoginTests.java +++ b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLoginTests.java @@ -17,14 +17,25 @@ package org.springframework.security.test.web.servlet.request; import org.junit.Before; import org.junit.Test; - +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor; import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.when; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; public class SecurityMockMvcRequestBuildersFormLoginTests { @@ -82,6 +93,31 @@ public class SecurityMockMvcRequestBuildersFormLoginTests { assertThat(request.getRequestURI()).isEqualTo("/uri-login/val1/val2"); } + /** + * spring-restdocs uses postprocessors to do its trick. It will work only if these are merged together + * with our request builders. (gh-7572) + * @throws Exception + */ + @Test + public void postProcessorsAreMergedDuringMockMvcPerform() throws Exception { + RequestPostProcessor postProcessor = mock(RequestPostProcessor.class); + when(postProcessor.postProcessRequest(any())).thenAnswer(i -> i.getArgument(0)); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new Object()) + .defaultRequest(MockMvcRequestBuilders.get("/").with(postProcessor)) + .build(); + + + MvcResult mvcResult = mockMvc.perform(formLogin()).andReturn(); + assertThat(mvcResult.getRequest().getMethod()).isEqualTo(HttpMethod.POST.name()); + assertThat(mvcResult.getRequest().getHeader("Accept")) + .isEqualTo(MediaType.toString(Arrays.asList(MediaType.APPLICATION_FORM_URLENCODED))); + assertThat(mvcResult.getRequest().getParameter("username")).isEqualTo("user"); + assertThat(mvcResult.getRequest().getParameter("password")).isEqualTo("password"); + assertThat(mvcResult.getRequest().getRequestURI()).isEqualTo("/login"); + assertThat(mvcResult.getRequest().getParameter("_csrf")).isNotEmpty(); + verify(postProcessor).postProcessRequest(any()); + } + // gh-3920 @Test public void usesAcceptMediaForContentNegotiation() { diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLogoutTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLogoutTests.java index 1e86868d3e..b6271e9409 100644 --- a/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLogoutTests.java +++ b/test/src/test/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestBuildersFormLogoutTests.java @@ -15,15 +15,28 @@ */ package org.springframework.security.test.web.servlet.request; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout; - import org.junit.Before; import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockServletContext; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor; import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout; public class SecurityMockMvcRequestBuildersFormLogoutTests { private MockServletContext servletContext; @@ -71,4 +84,25 @@ public class SecurityMockMvcRequestBuildersFormLogoutTests { assertThat(request.getRequestURI()).isEqualTo("/uri-logout/val1/val2"); } + /** + * spring-restdocs uses postprocessors to do its trick. It will work only if these are merged together + * with our request builders. (gh-7572) + * @throws Exception + */ + @Test + public void postProcessorsAreMergedDuringMockMvcPerform() throws Exception { + RequestPostProcessor postProcessor = mock(RequestPostProcessor.class); + when(postProcessor.postProcessRequest(any())).thenAnswer(i -> i.getArgument(0)); + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new Object()) + .defaultRequest(MockMvcRequestBuilders.get("/").with(postProcessor)) + .build(); + + MvcResult mvcResult = mockMvc.perform(logout()).andReturn(); + assertThat(mvcResult.getRequest().getMethod()).isEqualTo(HttpMethod.POST.name()); + assertThat(mvcResult.getRequest().getHeader("Accept")) + .isEqualTo(MediaType.toString(Arrays.asList(MediaType.TEXT_HTML, MediaType.ALL))); + assertThat(mvcResult.getRequest().getRequestURI()).isEqualTo("/logout"); + assertThat(mvcResult.getRequest().getParameter("_csrf")).isNotEmpty(); + verify(postProcessor).postProcessRequest(any()); + } } From 5343325091875cbc9edadad13f8893a2b82a0f3d Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 25 Jun 2020 11:30:12 -0500 Subject: [PATCH 142/348] Update to spring-build-conventions:0.0.33.RELEASE Closes gh-8759 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c1b838851d..078cc915e5 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.23.RELEASE' + classpath 'io.spring.gradle:spring-build-conventions:0.0.33.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" From 0356af03e68a1c0c66918360db765e14efa96b94 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 25 Jun 2020 11:32:18 -0500 Subject: [PATCH 143/348] Revert "Update to spring-build-conventions:0.0.33.RELEASE" This reverts commit 5343325091875cbc9edadad13f8893a2b82a0f3d. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 078cc915e5..c1b838851d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.33.RELEASE' + classpath 'io.spring.gradle:spring-build-conventions:0.0.23.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" From 5f395a351317768877f41f60f35873c4d37b2174 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 25 Jun 2020 11:34:46 -0500 Subject: [PATCH 144/348] Better scp Retry Settings --- docs/manual/spring-security-docs-manual.gradle | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/manual/spring-security-docs-manual.gradle b/docs/manual/spring-security-docs-manual.gradle index 4006bac83a..8056b92bf0 100644 --- a/docs/manual/spring-security-docs-manual.gradle +++ b/docs/manual/spring-security-docs-manual.gradle @@ -11,6 +11,13 @@ asciidoctor { 'gh-samples-url': "$ghUrl/samples" } +remotes { + docs { + retryCount = 5 // retry 5 times (default is 0) + retryWaitSec = 10 // wait 10 seconds between retries (default is 0) + } +} + docsZip { from(project(':spring-security-docs-guides').asciidoctor) { into 'guides' From 1bb49bbc2ea82e1d688bdc4b50b1f0749613c2f2 Mon Sep 17 00:00:00 2001 From: Ellie Bahadori Date: Wed, 17 Jun 2020 16:43:18 -0700 Subject: [PATCH 145/348] Use Github Actions workflow for PRs and remove Travis Closes gh-8718 --- .github/workflows/pr-build-workflow.yml | 22 ++++++++++++++++++++++ .travis.yml | 16 ---------------- README.adoc | 2 -- 3 files changed, 22 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/pr-build-workflow.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/pr-build-workflow.yml b/.github/workflows/pr-build-workflow.yml new file mode 100644 index 0000000000..db4f6ccbcf --- /dev/null +++ b/.github/workflows/pr-build-workflow.yml @@ -0,0 +1,22 @@ +name: PR Build + +on: pull_request + +jobs: + build: + name: Build + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '8' + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + - name: Build with Gradle + run: ./gradlew clean build --continue diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 981df90b0a..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: java - -jdk: - - openjdk8 - -os: - - linux - -before_cache: - - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock -cache: - directories: - - $HOME/.gradle/caches/ - - $HOME/.gradle/wrapper/ - -script: ./gradlew build --refresh-dependencies --no-daemon --continue diff --git a/README.adoc b/README.adoc index bbc983ce2f..79feb8370b 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,5 @@ image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge] -image:https://travis-ci.org/spring-projects/spring-security.svg?branch=master["Build Status", link="https://travis-ci.org/spring-projects/spring-security"] - = Spring Security Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 5.0 requires Spring 5.0 as From dcd2137418f8d35d340a9ba6bc6d09fe6d86dbcf Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 7 Jul 2020 11:43:49 -0500 Subject: [PATCH 146/348] LoginPageGeneratingWebFilter honors context path Closes gh-8807 --- .../ui/LoginPageGeneratingWebFilter.java | 6 +-- .../ui/LoginPageGeneratingWebFilterTests.java | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 web/src/test/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilterTests.java diff --git a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java index 5daa3f0012..d13a8489bd 100644 --- a/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilter.java @@ -105,7 +105,7 @@ public class LoginPageGeneratingWebFilter implements WebFilter { + " \n" + " \n" + "
\n" - + formLogin(queryParams, csrfTokenHtmlInput) + + formLogin(queryParams, contextPath, csrfTokenHtmlInput) + oauth2LoginLinks(queryParams, contextPath, this.oauth2AuthenticationUrlToClientName) + "
\n" + " \n" @@ -114,13 +114,13 @@ public class LoginPageGeneratingWebFilter implements WebFilter { return page.getBytes(Charset.defaultCharset()); } - private String formLogin(MultiValueMap queryParams, String csrfTokenHtmlInput) { + private String formLogin(MultiValueMap queryParams, String contextPath, String csrfTokenHtmlInput) { if (!this.formLoginEnabled) { return ""; } boolean isError = queryParams.containsKey("error"); boolean isLogoutSuccess = queryParams.containsKey("logout"); - return "
\n" + return " \n" + " \n" + createError(isError) + createLogoutSuccess(isLogoutSuccess) diff --git a/web/src/test/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilterTests.java new file mode 100644 index 0000000000..eeea8229df --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/server/ui/LoginPageGeneratingWebFilterTests.java @@ -0,0 +1,52 @@ +/* + * 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.web.server.ui; + +import org.junit.Test; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import reactor.core.publisher.Mono; + +import static org.assertj.core.api.Assertions.assertThat; + + +public class LoginPageGeneratingWebFilterTests { + + @Test + public void filterWhenLoginWithContextPathThenActionContainsContextPath() throws Exception { + LoginPageGeneratingWebFilter filter = new LoginPageGeneratingWebFilter(); + filter.setFormLoginEnabled(true); + + MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test/login").contextPath("/test")); + + filter.filter(exchange, e -> Mono.empty()).block(); + + assertThat(exchange.getResponse().getBodyAsString().block()).contains("action=\"/test/login\""); + } + + @Test + public void filterWhenLoginWithNoContextPathThenActionDoesNotContainsContextPath() throws Exception { + LoginPageGeneratingWebFilter filter = new LoginPageGeneratingWebFilter(); + filter.setFormLoginEnabled(true); + + MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/login")); + + filter.filter(exchange, e -> Mono.empty()).block(); + + assertThat(exchange.getResponse().getBodyAsString().block()).contains("action=\"/login\""); + } +} From 3dc59686fab6285b2474f2c61f20b34dd4feed02 Mon Sep 17 00:00:00 2001 From: wangsong Date: Tue, 7 Jul 2020 07:55:57 +0800 Subject: [PATCH 147/348] Fix ProviderManager Javadoc typo Closes gh-8800 --- .../security/authentication/ProviderManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java index ee487d472b..a1e2869bc0 100644 --- a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java +++ b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java @@ -213,7 +213,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar ((CredentialsContainer) result).eraseCredentials(); } - // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent + // If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it if (parentResult == null) { eventPublisher.publishAuthenticationSuccess(result); @@ -230,7 +230,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar "No AuthenticationProvider found for {0}")); } - // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent + // If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it if (parentException == null) { prepareException(lastException, authentication); From 65190293409adaae0a37f0d430747cac2d79a724 Mon Sep 17 00:00:00 2001 From: kothasa Date: Mon, 11 May 2020 12:32:56 +0100 Subject: [PATCH 148/348] Bearer Token Padding Closes gh-8502 --- .../web/DefaultBearerTokenResolver.java | 2 +- .../web/DefaultBearerTokenResolverTests.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java index 14c3a2558f..e8be1b11e8 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java @@ -100,7 +100,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver { throw new OAuth2AuthenticationException(error); } - return matcher.group("token"); + return authorization.substring(7); } return null; } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java index d048736942..6fc88569a2 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java @@ -51,6 +51,24 @@ public class DefaultBearerTokenResolverTests { assertThat(this.resolver.resolve(request)).isEqualTo(TEST_TOKEN); } + @Test + public void resolveWhenValidHeaderIsPresentWithSingleBytePaddingIndicatorThenTokenIsResolved() { + String token = TEST_TOKEN + "="; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + + assertThat(this.resolver.resolve(request)).isEqualTo(token); + } + + @Test + public void resolveWhenValidHeaderIsPresentWithTwoBytesPaddingIndicatorThenTokenIsResolved() { + String token = TEST_TOKEN + "=="; + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + + assertThat(this.resolver.resolve(request)).isEqualTo(token); + } + @Test public void resolveWhenLowercaseHeaderIsPresentThenTokenIsResolved() { MockHttpServletRequest request = new MockHttpServletRequest(); From 9d8920f1b12cabf43942ca2911d7eca189557988 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 15 Jul 2020 18:12:53 -0600 Subject: [PATCH 149/348] Polish Bearer Token Padding Issue gh-8502 --- .../web/DefaultBearerTokenResolver.java | 4 ++-- ...rverBearerTokenAuthenticationConverter.java | 2 +- .../web/DefaultBearerTokenResolverTests.java | 14 +++----------- ...earerTokenAuthenticationConverterTests.java | 18 +++++++++++++++--- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java index e8be1b11e8..2a38156ca4 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolver.java @@ -37,7 +37,7 @@ import org.springframework.util.StringUtils; public final class DefaultBearerTokenResolver implements BearerTokenResolver { private static final Pattern authorizationPattern = Pattern.compile( - "^Bearer (?[a-zA-Z0-9-._~+/]+)=*$", + "^Bearer (?[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE); private boolean allowFormEncodedBodyParameter = false; @@ -100,7 +100,7 @@ public final class DefaultBearerTokenResolver implements BearerTokenResolver { throw new OAuth2AuthenticationException(error); } - return authorization.substring(7); + return matcher.group("token"); } return null; } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java index b3aa15a829..2e4c296ae5 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java @@ -44,7 +44,7 @@ import java.util.regex.Pattern; public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter { private static final Pattern authorizationPattern = Pattern.compile( - "^Bearer (?[a-zA-Z0-9-._~+/]+)=*$", + "^Bearer (?[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE); private boolean allowUriQueryParameter = false; diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java index 6fc88569a2..6f9a7402bc 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/DefaultBearerTokenResolverTests.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. @@ -51,17 +51,9 @@ public class DefaultBearerTokenResolverTests { assertThat(this.resolver.resolve(request)).isEqualTo(TEST_TOKEN); } + // gh-8502 @Test - public void resolveWhenValidHeaderIsPresentWithSingleBytePaddingIndicatorThenTokenIsResolved() { - String token = TEST_TOKEN + "="; - MockHttpServletRequest request = new MockHttpServletRequest(); - request.addHeader("Authorization", "Bearer " + token); - - assertThat(this.resolver.resolve(request)).isEqualTo(token); - } - - @Test - public void resolveWhenValidHeaderIsPresentWithTwoBytesPaddingIndicatorThenTokenIsResolved() { + public void resolveWhenHeaderEndsWithPaddingIndicatorThenTokenIsResolved() { String token = TEST_TOKEN + "=="; MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Authorization", "Bearer " + token); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java index 1a36a2eef8..16e4740f34 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.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. @@ -16,8 +16,11 @@ package org.springframework.security.oauth2.server.resource.web.server; +import java.util.Base64; + import org.junit.Before; import org.junit.Test; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; @@ -27,8 +30,6 @@ import org.springframework.security.oauth2.server.resource.BearerTokenAuthentica import org.springframework.security.oauth2.server.resource.BearerTokenError; import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; -import java.util.Base64; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.catchThrowableOfType; @@ -56,6 +57,17 @@ public class ServerBearerTokenAuthenticationConverterTests { assertThat(convertToToken(request).getToken()).isEqualTo(TEST_TOKEN); } + // gh-8502 + @Test + public void resolveWhenHeaderEndsWithPaddingIndicatorThenTokenIsResolved() { + String token = TEST_TOKEN + "=="; + MockServerHttpRequest.BaseBuilder request = MockServerHttpRequest + .get("/") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + token); + + assertThat(convertToToken(request).getToken()).isEqualTo(token); + } + // gh-7011 @Test public void resolveWhenValidHeaderIsEmptyStringThenTokenIsResolved() { From 79d8b616f06da28d9b91c721a641bf36e1804e75 Mon Sep 17 00:00:00 2001 From: Romil Patel Date: Sat, 11 Jul 2020 11:44:47 +0530 Subject: [PATCH 150/348] WebSecurityConfigurerAdapter JavaDoc Closes gh-8784 --- .../configuration/WebSecurityConfigurerAdapter.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index 46e5fce86f..b29735b24f 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -330,6 +330,15 @@ public abstract class WebSecurityConfigurerAdapter implements /** * Override this method to configure {@link WebSecurity}. For example, if you wish to * ignore certain requests. + * + * Endpoint used in this method ignores the + * spring security filters, headers, csrf etc. see + * {@link org.springframework.security.config.annotation.web.configurers.HeadersConfigurer} and + * {@link org.springframework.security.config.annotation.web.configurers.CsrfConfigurer } + * + * Instead, if you want to protect public endpoints against common vulnerabilities, then see + * {@link #configure(HttpSecurity)} and the {@link HttpSecurity#authorizeRequests} + * configuration method. */ public void configure(WebSecurity web) throws Exception { } @@ -343,6 +352,10 @@ public abstract class WebSecurityConfigurerAdapter implements * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * * + * Public endpoints that require defense against common vulnerabilities can be specified here. + * See {@link HttpSecurity#authorizeRequests} and the `permitAll()` authorization rule + * for more details. + * * @param http the {@link HttpSecurity} to modify * @throws Exception if an error occurs */ From 0efdb2c92c97ccbedb4372cebd0415c4741188c3 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 20 Jul 2020 15:19:25 -0600 Subject: [PATCH 151/348] Polish WebSecurityConfigurerAdapter JavaDoc Issue gh-8784 --- .../configuration/WebSecurityConfigurerAdapter.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index b29735b24f..9499ff1ac2 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -331,12 +331,10 @@ public abstract class WebSecurityConfigurerAdapter implements * Override this method to configure {@link WebSecurity}. For example, if you wish to * ignore certain requests. * - * Endpoint used in this method ignores the - * spring security filters, headers, csrf etc. see - * {@link org.springframework.security.config.annotation.web.configurers.HeadersConfigurer} and - * {@link org.springframework.security.config.annotation.web.configurers.CsrfConfigurer } + * Endpoints specified in this method will be ignored by Spring Security, meaning it + * will not protect them from CSRF, XSS, Clickjacking, and so on. * - * Instead, if you want to protect public endpoints against common vulnerabilities, then see + * Instead, if you want to protect endpoints against common vulnerabilities, then see * {@link #configure(HttpSecurity)} and the {@link HttpSecurity#authorizeRequests} * configuration method. */ @@ -352,9 +350,9 @@ public abstract class WebSecurityConfigurerAdapter implements * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic(); * * - * Public endpoints that require defense against common vulnerabilities can be specified here. + * Any endpoint that requires defense against common vulnerabilities can be specified here, including public ones. * See {@link HttpSecurity#authorizeRequests} and the `permitAll()` authorization rule - * for more details. + * for more details on public endpoints. * * @param http the {@link HttpSecurity} to modify * @throws Exception if an error occurs From 2f80b8a5be902eac88a6921255506b4ce5f44fde Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 30 Jul 2020 16:21:28 -0600 Subject: [PATCH 152/348] Additional Jwt Validation Debug Messages Closes gh-8589 Co-authored-by: MattyA --- .../oauth2/jwt/JwtIssuerValidator.java | 6 +++ .../oauth2/jwt/JwtTimestampValidator.java | 38 +++++++++++-------- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java index 49a5845fec..b0a44c7789 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtIssuerValidator.java @@ -15,6 +15,9 @@ */ package org.springframework.security.oauth2.jwt; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; @@ -28,6 +31,8 @@ import org.springframework.util.Assert; * @since 5.1 */ public final class JwtIssuerValidator implements OAuth2TokenValidator { + private final Log logger = LogFactory.getLog(getClass()); + private static OAuth2Error INVALID_ISSUER = new OAuth2Error( OAuth2ErrorCodes.INVALID_REQUEST, @@ -57,6 +62,7 @@ public final class JwtIssuerValidator implements OAuth2TokenValidator { if (this.issuer.equals(tokenIssuer)) { return OAuth2TokenValidatorResult.success(); } else { + logger.debug(INVALID_ISSUER.getDescription()); return OAuth2TokenValidatorResult.failure(INVALID_ISSUER); } } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java index 1c8356851b..f5c1e08155 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java @@ -15,17 +15,20 @@ */ package org.springframework.security.oauth2.jwt; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; - +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.util.Assert; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; + /** * An implementation of {@link OAuth2TokenValidator} for verifying claims in a Jwt-based access token * @@ -41,6 +44,8 @@ import org.springframework.util.Assert; * @see JSON Web Token (JWT) */ public final class JwtTimestampValidator implements OAuth2TokenValidator { + private final Log logger = LogFactory.getLog(getClass()); + private static final Duration DEFAULT_MAX_CLOCK_SKEW = Duration.of(60, ChronoUnit.SECONDS); private final Duration clockSkew; @@ -56,7 +61,6 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { public JwtTimestampValidator(Duration clockSkew) { Assert.notNull(clockSkew, "clockSkew cannot be null"); - this.clockSkew = clockSkew; } @@ -71,11 +75,8 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { if (expiry != null) { if (Instant.now(this.clock).minus(clockSkew).isAfter(expiry)) { - OAuth2Error error = new OAuth2Error( - OAuth2ErrorCodes.INVALID_REQUEST, - String.format("Jwt expired at %s", jwt.getExpiresAt()), - "https://tools.ietf.org/html/rfc6750#section-3.1"); - return OAuth2TokenValidatorResult.failure(error); + OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt expired at %s", jwt.getExpiresAt())); + return OAuth2TokenValidatorResult.failure(oAuth2Error); } } @@ -83,17 +84,22 @@ public final class JwtTimestampValidator implements OAuth2TokenValidator { if (notBefore != null) { if (Instant.now(this.clock).plus(clockSkew).isBefore(notBefore)) { - OAuth2Error error = new OAuth2Error( - OAuth2ErrorCodes.INVALID_REQUEST, - String.format("Jwt used before %s", jwt.getNotBefore()), - "https://tools.ietf.org/html/rfc6750#section-3.1"); - return OAuth2TokenValidatorResult.failure(error); + OAuth2Error oAuth2Error = createOAuth2Error(String.format("Jwt used before %s", jwt.getNotBefore())); + return OAuth2TokenValidatorResult.failure(oAuth2Error); } } return OAuth2TokenValidatorResult.success(); } + private OAuth2Error createOAuth2Error(String reason) { + logger.debug(reason); + return new OAuth2Error( + OAuth2ErrorCodes.INVALID_REQUEST, + reason, + "https://tools.ietf.org/html/rfc6750#section-3.1"); + } + /** * ' * Use this {@link Clock} with {@link Instant#now()} for assessing From 510d1b81213719302ec441943be8aba874498601 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 30 Jul 2020 16:22:49 -0600 Subject: [PATCH 153/348] Polish to Avoid NPE Issue gh-5648 Co-authored-by: MattyA --- .../security/oauth2/jwt/NimbusJwtDecoder.java | 15 +++++++++++++-- .../oauth2/jwt/NimbusReactiveJwtDecoder.java | 17 +++++++++++++---- .../oauth2/jwt/NimbusJwtDecoderTests.java | 16 ++++++++++++++++ .../jwt/NimbusReactiveJwtDecoderTests.java | 16 ++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 8478a58bf6..df0def5233 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java @@ -22,6 +22,7 @@ import java.net.URL; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -54,11 +55,13 @@ import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; @@ -164,9 +167,17 @@ public final class NimbusJwtDecoder implements JwtDecoder { private Jwt validateJwt(Jwt jwt){ OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt); if (result.hasErrors()) { - String description = result.getErrors().iterator().next().getDescription(); + Collection errors = result.getErrors(); + String validationErrorString = "Unable to validate Jwt"; + for (OAuth2Error oAuth2Error : errors) { + if (!StringUtils.isEmpty(oAuth2Error.getDescription())) { + validationErrorString = String.format( + DECODING_ERROR_MESSAGE_TEMPLATE, oAuth2Error.getDescription()); + break; + } + } throw new JwtValidationException( - String.format(DECODING_ERROR_MESSAGE_TEMPLATE, description), + validationErrorString, result.getErrors()); } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java index 1779d49f50..d485877311 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoder.java @@ -16,6 +16,7 @@ package org.springframework.security.oauth2.jwt; import java.security.interfaces.RSAPublicKey; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -51,12 +52,14 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.convert.converter.Converter; +import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jose.jws.JwsAlgorithm; import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.client.WebClient; /** @@ -172,10 +175,16 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder { private Jwt validateJwt(Jwt jwt) { OAuth2TokenValidatorResult result = this.jwtValidator.validate(jwt); - - if ( result.hasErrors() ) { - String message = result.getErrors().iterator().next().getDescription(); - throw new JwtValidationException(message, result.getErrors()); + if (result.hasErrors()) { + Collection errors = result.getErrors(); + String validationErrorString = "Unable to validate Jwt"; + for (OAuth2Error oAuth2Error : errors) { + if (!StringUtils.isEmpty(oAuth2Error.getDescription())) { + validationErrorString = oAuth2Error.getDescription(); + break; + } + } + throw new JwtValidationException(validationErrorString, errors); } return jwt; diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index c6fec7d112..b63cbec563 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java @@ -193,6 +193,22 @@ public class NimbusJwtDecoderTests { .hasFieldOrPropertyWithValue("errors", Arrays.asList(firstFailure, secondFailure)); } + @Test + public void decodeWhenReadingErrorPickTheFirstErrorMessage() { + OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); + this.jwtDecoder.setJwtValidator(jwtValidator); + + OAuth2Error errorEmpty = new OAuth2Error("mock-error", "", "mock-uri"); + OAuth2Error error = new OAuth2Error("mock-error", "mock-description", "mock-uri"); + OAuth2Error error2 = new OAuth2Error("mock-error-second", "mock-description-second", "mock-uri-second"); + OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(errorEmpty, error, error2); + when(jwtValidator.validate(any(Jwt.class))).thenReturn(result); + + Assertions.assertThatCode(() -> this.jwtDecoder.decode(SIGNED_JWT)) + .isInstanceOf(JwtValidationException.class) + .hasMessageContaining("mock-description"); + } + @Test public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() { Converter, Map> claimSetConverter = mock(Converter.class); diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java index 59a361141a..0763764b99 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusReactiveJwtDecoderTests.java @@ -221,6 +221,22 @@ public class NimbusReactiveJwtDecoderTests { .hasMessageContaining("mock-description"); } + @Test + public void decodeWhenReadingErrorPickTheFirstErrorMessage() { + OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); + this.decoder.setJwtValidator(jwtValidator); + + OAuth2Error errorEmpty = new OAuth2Error("mock-error", "", "mock-uri"); + OAuth2Error error = new OAuth2Error("mock-error", "mock-description", "mock-uri"); + OAuth2Error error2 = new OAuth2Error("mock-error-second", "mock-description-second", "mock-uri-second"); + OAuth2TokenValidatorResult result = OAuth2TokenValidatorResult.failure(errorEmpty, error, error2); + when(jwtValidator.validate(any(Jwt.class))).thenReturn(result); + + assertThatCode(() -> this.decoder.decode(this.messageReadToken).block()) + .isInstanceOf(JwtValidationException.class) + .hasMessageContaining("mock-description"); + } + @Test public void decodeWhenUsingSignedJwtThenReturnsClaimsGivenByClaimSetConverter() { Converter, Map> claimSetConverter = mock(Converter.class); From fd669f751dc9ae9c6f64f17a2dd2aac9863d32cf Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 31 Jul 2020 08:37:32 -0600 Subject: [PATCH 154/348] Remove unused import Issue gh-8589 --- .../security/oauth2/jwt/JwtTimestampValidator.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java index f5c1e08155..eef9dc8e8e 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtTimestampValidator.java @@ -15,20 +15,20 @@ */ package org.springframework.security.oauth2.jwt; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.util.Assert; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; - /** * An implementation of {@link OAuth2TokenValidator} for verifying claims in a Jwt-based access token * From da4bd22c6d0c9c27bf8eb842a49533166cc714d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1=C4=8D?= Date: Sun, 2 Aug 2020 13:16:38 +0200 Subject: [PATCH 155/348] Resolve Bearer token after subscribing to publisher Bearer token was resolved immediately after calling method convert. In situations when malformed token was provided or authorization header and access token query param were present in request exception was thrown instead of signalling error. After this change Bearer token is resolved on subscription and invalid states are handled by signaling error to subscriber. Closes gh-8865 --- .../ServerBearerTokenAuthenticationConverter.java | 2 +- ...ServerBearerTokenAuthenticationConverterTests.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java index 2e4c296ae5..39198d6257 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java @@ -50,7 +50,7 @@ public class ServerBearerTokenAuthenticationConverter private boolean allowUriQueryParameter = false; public Mono convert(ServerWebExchange exchange) { - return Mono.justOrEmpty(token(exchange.getRequest())) + return Mono.fromCallable(() -> token(exchange.getRequest())) .map(token -> { if (token.isEmpty()) { BearerTokenError error = invalidTokenError(); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java index 16e4740f34..03d663f0cb 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java @@ -131,6 +131,17 @@ public class ServerBearerTokenAuthenticationConverterTests { .hasMessageContaining(("Bearer token is malformed")); } + // gh-8865 + @Test + public void resolveWhenHeaderWithInvalidCharactersIsPresentAndNotSubscribedThenNoneExceptionIsThrown() { + MockServerHttpRequest.BaseBuilder request = MockServerHttpRequest + .get("/") + .header(HttpHeaders.AUTHORIZATION, "Bearer an\"invalid\"token"); + + assertThatCode(() -> this.converter.convert(MockServerWebExchange.from(request))) + .doesNotThrowAnyException(); + } + @Test public void resolveWhenValidHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() { MockServerHttpRequest.BaseBuilder request = MockServerHttpRequest From 6512c04079bdb3dfe5888dda4c1a1e600c738d57 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 10:37:32 +0200 Subject: [PATCH 156/348] Upgrade to embedded Apache Tomcat 9.0.37 Closes gh-8913 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index abb33be08f..61a9078e84 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.34' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.34' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.34' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.34' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.37' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.37' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.37' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.37' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.34' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.37' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From b425b622dba3744e68e7e8b45bf01531692f1a34 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 10:41:59 +0200 Subject: [PATCH 157/348] Update to GAE 1.9.81 Closes gh-8911 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bc1c8a7aea..280e6553cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 -gaeVersion=1.9.80 +gaeVersion=1.9.81 springBootVersion=2.2.7.RELEASE version=5.2.6.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From b1e09eddaab3ce91782ed807cd32f2893e5cea3e Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:04:25 +0200 Subject: [PATCH 158/348] Update to Jackson 2.10.5 Closes gh-8910 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 61a9078e84..b5404b2524 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -42,9 +42,9 @@ dependencyManagement { dependency 'asm:asm:3.1' dependency 'ch.qos.logback:logback-classic:1.2.3' dependency 'ch.qos.logback:logback-core:1.2.3' - dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.4' - dependency 'com.fasterxml.jackson.core:jackson-core:2.10.4' - dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.4' + dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.5' + dependency 'com.fasterxml.jackson.core:jackson-core:2.10.5' + dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.5' dependency 'com.fasterxml:classmate:1.3.4' dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1' dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion' From b4d2217d452358ceb21fd9d79188a37d4560dcf4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:06:50 +0200 Subject: [PATCH 159/348] Update to jaxb-impl 2.3.3 Closes gh-8912 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index b5404b2524..d4c51e5734 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -61,7 +61,7 @@ dependencyManagement { dependency 'com.squareup.okhttp3:okhttp:3.14.9' dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' - dependency 'com.sun.xml.bind:jaxb-impl:2.3.2' + dependency 'com.sun.xml.bind:jaxb-impl:2.3.3' dependency 'com.unboundid:unboundid-ldapsdk:4.0.14' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'commons-cli:commons-cli:1.4' From 669fdba98c8c25ba58b6a30abe3bf10c168ebb83 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:21:45 +0200 Subject: [PATCH 160/348] Update to Spring Boot 2.2.9.RELEASE Closes gh-8921 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 280e6553cd..dc6f0bdf7d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.81 -springBootVersion=2.2.7.RELEASE +springBootVersion=2.2.9.RELEASE version=5.2.6.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From b408f074d5e449c349e41e904a9e97c16ec4e970 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:22:31 +0200 Subject: [PATCH 161/348] Update to Reactor Dysprosium-SR10 Closes gh-8920 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d4c51e5734..45d0719b1a 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR7' + ext.reactorVersion = 'Dysprosium-SR10' } if (!project.hasProperty('springVersion')) { From 8dfe0fa815a8c2074c4841d49bd3136d917d50a3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:22:53 +0200 Subject: [PATCH 162/348] Update to Spring Framework 5.2.8.RELEASE Closes gh-8919 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 45d0719b1a..9abe6e9bfa 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.6.RELEASE' + ext.springVersion = '5.2.8.RELEASE' } if (!project.hasProperty('springDataVersion')) { From 22a1bce83cf427b988fc0a1b9a258ddea1809425 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:23:21 +0200 Subject: [PATCH 163/348] Update to Spring Data Moore-SR9 Closes gh-8918 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 9abe6e9bfa..8dbccef9a6 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR7' + ext.springDataVersion = 'Moore-SR9' } ext.rsocketVersion = '1.0.1' From fea90f932854d6f30abd674b171eb892c65dd0e4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:23:56 +0200 Subject: [PATCH 164/348] Update to PowerMock Mockito2 2.0.7 Closes gh-8917 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 8dbccef9a6..2fe3b7e18a 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -24,7 +24,7 @@ dependencyManagement { dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' - dependency 'org.powermock:powermock-api-mockito2:2.0.6' + dependency 'org.powermock:powermock-api-mockito2:2.0.7' dependency 'org.powermock:powermock-api-support:2.0.7' dependency 'org.powermock:powermock-core:2.0.7' dependency 'org.powermock:powermock-module-junit4-common:2.0.7' From 9fbed06b6bbc47c5da0997a127102b25838854cf Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:24:16 +0200 Subject: [PATCH 165/348] Update blockhound to 1.0.4.RELEASE Closes gh-8916 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 2fe3b7e18a..04d2681871 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -72,7 +72,7 @@ dependencyManagement { dependency 'commons-lang:commons-lang:2.6' dependency 'commons-logging:commons-logging:1.2' dependency 'dom4j:dom4j:1.6.1' - dependency 'io.projectreactor.tools:blockhound:1.0.3.RELEASE' + dependency 'io.projectreactor.tools:blockhound:1.0.4.RELEASE' dependency "io.rsocket:rsocket-core:${rsocketVersion}" dependency "io.rsocket:rsocket-transport-netty:${rsocketVersion}" dependency 'javax.activation:activation:1.1.1' From 4a1b96d51a99fa87ea1fc05e99907f77004e2dd9 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:24:47 +0200 Subject: [PATCH 166/348] Update to embedded Tomcat websocket 8.5.57 Closes gh-8914 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 04d2681871..e3bf86b41b 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -149,7 +149,7 @@ dependencyManagement { dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.37' dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.37' dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.37' - dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.23' + dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57' dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.37' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" From acbbb6941cb63df86afeb17c850dc61cfab5561c Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 11:25:05 +0200 Subject: [PATCH 167/348] Update to groovy 2.4.20 Closes gh-8915 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e3bf86b41b..d094442799 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -158,9 +158,9 @@ dependencyManagement { dependency 'org.attoparser:attoparser:2.0.4.RELEASE' dependency 'org.bouncycastle:bcpkix-jdk15on:1.64' dependency 'org.bouncycastle:bcprov-jdk15on:1.64' - dependency 'org.codehaus.groovy:groovy-all:2.4.19' - dependency 'org.codehaus.groovy:groovy-json:2.4.19' - dependency 'org.codehaus.groovy:groovy:2.4.19' + dependency 'org.codehaus.groovy:groovy-all:2.4.20' + dependency 'org.codehaus.groovy:groovy-json:2.4.20' + dependency 'org.codehaus.groovy:groovy:2.4.20' dependency 'org.eclipse.jdt:ecj:3.12.3' dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.27.v20200227' dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227' From 04d6d5ae7423bd9888741be4012b952541507c28 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 13:47:12 +0200 Subject: [PATCH 168/348] Update to nohttp 0.0.5.RELEASE Closes gh-8927 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c1b838851d..186a716278 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { dependencies { classpath 'io.spring.gradle:spring-build-conventions:0.0.23.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" - classpath 'io.spring.nohttp:nohttp-gradle:0.0.2.RELEASE' + classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" } repositories { From 2443675a595cea19320b65e61232d7d583dd20fc Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 15:34:14 +0200 Subject: [PATCH 169/348] Release 5.2.6.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index dc6f0bdf7d..957adfea2b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.81 springBootVersion=2.2.9.RELEASE -version=5.2.6.BUILD-SNAPSHOT +version=5.2.6.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 8c7dbf2e3daa30280494b136c7afe3e2115d1c3d Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 5 Aug 2020 17:16:34 +0200 Subject: [PATCH 170/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 957adfea2b..6578c1f96d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.5 gaeVersion=1.9.81 springBootVersion=2.2.9.RELEASE -version=5.2.6.RELEASE +version=5.2.7.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 85889d5e0b7a28464ce03f68da43ae4bf2a4915c Mon Sep 17 00:00:00 2001 From: Tomoki Tsubaki Date: Wed, 16 Sep 2020 21:52:26 +0900 Subject: [PATCH 171/348] Create the CSRF token on the bounded elactic scheduler The CSRF token is generated by UUID.randomUUID() which is I/O blocking operation. This commit changes the subscriber thread to the bounded elactic scheduler. Closes gh-9018 --- .../web/server/csrf/CookieServerCsrfTokenRepository.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java index f1ed61ee17..04328e228f 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java @@ -26,6 +26,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; /** * A {@link ServerCsrfTokenRepository} that persists the CSRF token in a cookie named "XSRF-TOKEN" and @@ -62,7 +63,7 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep @Override public Mono generateToken(ServerWebExchange exchange) { - return Mono.fromCallable(this::createCsrfToken); + return Mono.fromCallable(this::createCsrfToken).subscribeOn(Schedulers.boundedElastic()); } @Override From 89ad42a06fe5f401f48602c7bf1eeee4ce331f42 Mon Sep 17 00:00:00 2001 From: ilee Date: Sun, 20 Sep 2020 15:43:42 +0900 Subject: [PATCH 172/348] Update ssl setup guide link in tomcat server --- .../docs/asciidoc/_includes/servlet/authentication/x509.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/x509.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/x509.adoc index 615d8bab2e..f71b0f2e09 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/x509.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/x509.adoc @@ -15,7 +15,7 @@ It maps the certificate to an application user and loads that user's set of gran You should be familiar with using certificates and setting up client authentication for your servlet container before attempting to use it with Spring Security. Most of the work is in creating and installing suitable certificates and keys. -For example, if you're using Tomcat then read the instructions here https://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html[https://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html]. +For example, if you're using Tomcat then read the instructions here https://tomcat.apache.org/tomcat-9.0-doc/ssl-howto.html[https://tomcat.apache.org/tomcat-9.0-doc/ssl-howto.html]. It's important that you get this working before trying it out with Spring Security From 6dad918e7bb42e82e42ccba617bb80b5cf1a7ad3 Mon Sep 17 00:00:00 2001 From: Artem Grankin Date: Fri, 25 Sep 2020 19:31:05 +0200 Subject: [PATCH 173/348] Replace expired msdn link with latest web archive copy Initial link expired in March, 2016. Latest copy found in web archive is from February, 2016 --- .../config/annotation/web/configurers/HeadersConfigurer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java index adcffa0648..82da8cd401 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java @@ -183,7 +183,7 @@ public class HeadersConfigurer> extends * *

* Allows customizing the {@link XXssProtectionHeaderWriter} which adds the X-XSS-Protection header *

* @@ -198,7 +198,7 @@ public class HeadersConfigurer> extends * *

* Allows customizing the {@link XXssProtectionHeaderWriter} which adds the X-XSS-Protection header *

* From bd3e3ed2c5ee87450bcba8b0490a7dba5bae0db4 Mon Sep 17 00:00:00 2001 From: Malyshau Stanislau Date: Sun, 27 Sep 2020 21:01:18 +0300 Subject: [PATCH 174/348] Add try-with-resources to close stream Closes gh-9041 --- .../security/core/SpringSecurityCoreVersion.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java b/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java index 10547903da..bff72c22f1 100644 --- a/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java +++ b/core/src/main/java/org/springframework/security/core/SpringSecurityCoreVersion.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.SpringVersion; import java.io.IOException; +import java.io.InputStream; import java.util.Properties; /** @@ -108,8 +109,9 @@ public class SpringSecurityCoreVersion { */ private static String getSpringVersion() { Properties properties = new Properties(); - try { - properties.load(SpringSecurityCoreVersion.class.getClassLoader().getResourceAsStream("META-INF/spring-security.versions")); + try (InputStream is = SpringSecurityCoreVersion.class.getClassLoader() + .getResourceAsStream("META-INF/spring-security.versions")) { + properties.load(is); } catch (IOException | NullPointerException e) { return null; } From 925a6685d99fbd024a7b74c3629a4f1ee57e5083 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 14:30:11 +0200 Subject: [PATCH 175/348] Update to org.aspectj 1.9.6 Closes gh-9079 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 6578c1f96d..30ce86b06b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -aspectjVersion=1.9.5 +aspectjVersion=1.9.6 gaeVersion=1.9.81 springBootVersion=2.2.9.RELEASE version=5.2.7.BUILD-SNAPSHOT From 6a87ccca4abd4bebe0c878ed6e93a4df4015375c Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 14:34:12 +0200 Subject: [PATCH 176/348] Update to GAE 1.9.82 Closes gh-9080 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 30ce86b06b..3035a711c3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 -gaeVersion=1.9.81 +gaeVersion=1.9.82 springBootVersion=2.2.9.RELEASE version=5.2.7.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 58b68625bbc01220fce97db85e860acd3b05667c Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:08:58 +0200 Subject: [PATCH 177/348] Update to Spring Boot 2.2.10 Closes gh-9081 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3035a711c3..68dcfc7d95 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.82 -springBootVersion=2.2.9.RELEASE +springBootVersion=2.2.10.RELEASE version=5.2.7.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 984fdd4c00de2eebbf224b09b6d6f3b9fdf75ea1 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:09:40 +0200 Subject: [PATCH 178/348] Update to Reactor Dysprosium-SR12 Closes gh-9082 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d094442799..e2937c62db 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR10' + ext.reactorVersion = 'Dysprosium-SR12' } if (!project.hasProperty('springVersion')) { From 5d0eb32f4d27ba5124e1cef8ac6e1669965c2c76 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:10:10 +0200 Subject: [PATCH 179/348] Update to Spring Framework 5.2.9 Closes gh-9083 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e2937c62db..d96342de6b 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.8.RELEASE' + ext.springVersion = '5.2.9.RELEASE' } if (!project.hasProperty('springDataVersion')) { From dba3dacc19712d62b85dfb70c3b88e82fb1dd458 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:11:44 +0200 Subject: [PATCH 180/348] Update to Spring Data Moore-SR10 Closes gh-9088 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d96342de6b..7d9a7e4a6d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR9' + ext.springDataVersion = 'Moore-SR10' } ext.rsocketVersion = '1.0.1' From 7181f576fc905af23b6fc503e2d93eebc38e0d83 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:12:06 +0200 Subject: [PATCH 181/348] Update to RSocket 1.0.2 Closes gh-9084 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 7d9a7e4a6d..95f0ad34f6 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR10' } -ext.rsocketVersion = '1.0.1' +ext.rsocketVersion = '1.0.2' dependencyManagement { imports { From 8b3777d0f188478fc6e24be5db382d80971cf90e Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:12:37 +0200 Subject: [PATCH 182/348] Upgrade to embedded Apache Tomcat 9.0.38 Closes gh-9085 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 95f0ad34f6..43eb655a14 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.37' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.37' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.37' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.37' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.38' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.38' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.38' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.38' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.37' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.38' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From cb055f14021dacd2e1987e28fb4239823f330bc3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:13:17 +0200 Subject: [PATCH 183/348] Update to Hibernate Entity manager 5.4.22 Closes gh-9087 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 43eb655a14..417c5414fc 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.13.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.22.Final' dependency 'org.hibernate:hibernate-validator:6.1.2.Final' dependency 'org.hsqldb:hsqldb:2.5.0' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From 9c5ce287d708ac38906cf1889a7c9c12b3ab607b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 2 Oct 2020 17:13:54 +0200 Subject: [PATCH 184/348] Update to Hibernate Validator 6.1.6 Closes gh-9086 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 417c5414fc..2a58adc264 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -183,7 +183,7 @@ dependencyManagement { dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' dependency 'org.hibernate:hibernate-entitymanager:5.4.22.Final' - dependency 'org.hibernate:hibernate-validator:6.1.2.Final' + dependency 'org.hibernate:hibernate-validator:6.1.6.Final' dependency 'org.hsqldb:hsqldb:2.5.0' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.javassist:javassist:3.22.0-CR2' From 52814ab387603ddfe8e79df174fad85b25692dc7 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 7 Oct 2020 10:49:34 -0600 Subject: [PATCH 185/348] Release 5.2.7.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 68dcfc7d95..98c6ae31ea 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.82 springBootVersion=2.2.10.RELEASE -version=5.2.7.BUILD-SNAPSHOT +version=5.2.7.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 21f7187f409626edfc40c0df937e5b20a381f22a Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 7 Oct 2020 11:37:33 -0600 Subject: [PATCH 186/348] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 98c6ae31ea..4c60238a31 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.82 springBootVersion=2.2.10.RELEASE -version=5.2.7.RELEASE +version=5.2.8.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 0462c422909d9c3f57d2823d58fd531934851216 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 12 Oct 2020 16:50:51 -0600 Subject: [PATCH 187/348] Update Test Controllers Closes gh-9121 --- .../security/config/http/MiscHttpConfigTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java index c4903195ab..9320052235 100644 --- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java +++ b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java @@ -822,19 +822,19 @@ public class MiscHttpConfigTests { @RestController static class AuthenticationController { @GetMapping("/password") - public String password(@AuthenticationPrincipal Authentication authentication) { + public String password(Authentication authentication) { return (String) authentication.getCredentials(); } @GetMapping("/roles") - public String roles(@AuthenticationPrincipal Authentication authentication) { + public String roles(Authentication authentication) { return authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); } @GetMapping("/details") - public String details(@AuthenticationPrincipal Authentication authentication) { + public String details(Authentication authentication) { return authentication.getDetails().getClass().getName(); } } From 21c7f7518fef4f20abfe229b31eec1fc4b78c7f1 Mon Sep 17 00:00:00 2001 From: Ayush Kohli Date: Sun, 11 Oct 2020 13:21:22 -0500 Subject: [PATCH 188/348] Closes gh-8196 Add leveloffset --- .../src/docs/asciidoc/_includes/servlet/appendix/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc index f0a169ecfb..63dd72fcbd 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/index.adoc @@ -1,7 +1,7 @@ = Appendix -include::database-schema.adoc[] +include::database-schema.adoc[leveloffset=+1] include::namespace.adoc[] From ec7deca76fb2530ca6348e2e4adfcfe954c71949 Mon Sep 17 00:00:00 2001 From: Hideaki Matsunami Date: Thu, 22 Oct 2020 13:55:19 +0900 Subject: [PATCH 189/348] add white space before strong notation. --- .../src/docs/asciidoc/_includes/servlet/appendix/faq.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc index 01c1c23f7d..1ac278566f 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/appendix/faq.adoc @@ -233,7 +233,7 @@ You cannot have two separate sessions at once. So if you log in again in another window or tab you are just reauthenticating in the same session. The server doesn't know anything about tabs, windows or browser instances. All it sees are HTTP requests and it ties those to a particular session according to the value of the JSESSIONID cookie that they contain. -When a user authenticates during a session, Spring Security's concurrent session control checks the number of__other authenticated sessions__ that they have. +When a user authenticates during a session, Spring Security's concurrent session control checks the number of __other authenticated sessions__ that they have. If they are already authenticated with the same session, then re-authenticating will have no effect. From 03c2cc846d5dad2b1c666193ccc4aecc84fa5311 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 16 Nov 2020 15:40:31 -0600 Subject: [PATCH 190/348] Use artifactoryUsername/Password for plugin repositories --- build.gradle | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 186a716278..98f7881dd2 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,15 @@ buildscript { classpath "io.freefair.gradle:aspectj-plugin:4.0.2" } repositories { - maven { url 'https://repo.spring.io/plugins-snapshot' } + maven { + url = 'https://repo.spring.io/plugins-snapshot' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username "$artifactoryUsername" + password "$artifactoryPassword" + } + } + } maven { url 'https://plugins.gradle.org/m2/' } } } From 05c0176b98778f2a0045350c9497559f577ab38d Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 17 Nov 2020 09:02:21 -0600 Subject: [PATCH 191/348] allprojects uses artifactoryUsername/Password --- build.gradle | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/build.gradle b/build.gradle index 98f7881dd2..623728adcb 100644 --- a/build.gradle +++ b/build.gradle @@ -43,3 +43,19 @@ subprojects { options.encoding = "UTF-8" } } + +if (project.hasProperty('artifactoryUsername') { + allprojects { project -> + project.repositories { repos -> + all { repo -> + if (!repo.url.toString().startsWith("https://repo.spring.io/")) { + return; + } + repo.credentials { + username = artifactoryUsername + password = artifactoryPassword + } + } + } + } +} From 07479dce6d245366be52de9360b7168ed5f580a0 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 17 Nov 2020 09:02:49 -0600 Subject: [PATCH 192/348] Use artifactoryUsername/Password in Jenkinsfile --- Jenkinsfile | 50 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 681a6f31eb..47128e7cd6 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,6 +8,9 @@ properties(projectProperties) def SUCCESS = hudson.model.Result.SUCCESS.toString() currentBuild.result = SUCCESS + +def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD') + try { parallel check: { stage('Check') { @@ -15,10 +18,13 @@ try { checkout scm sh "git clean -dfx" try { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew clean check --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { + sh "./gradlew clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + } } } catch(Exception e) { + currentBuild.result = 'FAILED: check' throw e } finally { @@ -34,11 +40,13 @@ try { sh "git clean -dfx" withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) { try { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - if ("master" == env.BRANCH_NAME) { - sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" - } else { - sh "./gradlew sonarqube -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { + if ("master" == env.BRANCH_NAME) { + sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" + } else { + sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" + } } } } catch(Exception e) { @@ -55,8 +63,10 @@ try { checkout scm sh "git clean -dfx" try { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew clean test -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { + sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace" + } } } catch(Exception e) { currentBuild.result = 'FAILED: snapshots' @@ -72,7 +82,7 @@ try { sh "git clean -dfx" try { withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) { - sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } catch(Exception e) { currentBuild.result = 'FAILED: jdk9' @@ -87,8 +97,10 @@ try { checkout scm sh "git clean -dfx" try { - withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) { - sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) { + sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + } } } catch(Exception e) { currentBuild.result = 'FAILED: jdk10' @@ -103,8 +115,10 @@ try { checkout scm sh "git clean -dfx" try { - withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) { - sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) { + sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + } } } catch(Exception e) { currentBuild.result = 'FAILED: jdk11' @@ -119,8 +133,10 @@ try { checkout scm sh "git clean -dfx" try { - withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) { - sh "./gradlew clean test --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) { + sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + } } } catch(Exception e) { currentBuild.result = 'FAILED: jdk12' @@ -139,7 +155,7 @@ try { withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) { withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) { withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { - withCredentials([usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD')]) { + withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } From 1bac5498dde5323c78e3c82d2eebb1a97c30a3e1 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 17 Nov 2020 09:05:37 -0600 Subject: [PATCH 193/348] Fix artifactoryUsername/Password in build.gradle Missing ) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 623728adcb..25457ad21f 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ subprojects { } } -if (project.hasProperty('artifactoryUsername') { +if (project.hasProperty('artifactoryUsername')) { allprojects { project -> project.repositories { repos -> all { repo -> From 5948f492f55b5b2c6f92aa24ce6a237c069c21eb Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 17 Nov 2020 09:28:13 -0600 Subject: [PATCH 194/348] Add missing withCredentials in Jenkinsfile --- Jenkinsfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 47128e7cd6..4c694f5de3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -81,8 +81,10 @@ try { checkout scm sh "git clean -dfx" try { - withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) { - sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) { + sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + } } } catch(Exception e) { currentBuild.result = 'FAILED: jdk9' From e4b538146a917a8420e9fac28361ed71dc4981ef Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:10:34 -0700 Subject: [PATCH 195/348] Update to Spring Boot 2.2.11 Closes gh-9233 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4c60238a31..36f90ff913 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.82 -springBootVersion=2.2.10.RELEASE +springBootVersion=2.2.11.RELEASE version=5.2.8.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 276a934bcd89e914bf7ab9fe4901d0404cdc50fc Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:11:44 -0700 Subject: [PATCH 196/348] Update to Spring 5.2.11 Closes gh-9234 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 2a58adc264..83753287f6 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.9.RELEASE' + ext.springVersion = '5.2.11.RELEASE' } if (!project.hasProperty('springDataVersion')) { From ebb2afa5e8a2e0034874b0ea46341aa95de7cb5b Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:13:38 -0700 Subject: [PATCH 197/348] Update to Spring Data Moore-SR11 Closes gh-9235 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 83753287f6..91a26df99d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR10' + ext.springDataVersion = 'Moore-SR11' } ext.rsocketVersion = '1.0.2' From 83a177d327017e9aa2af7970d7ad309f7c63d5f4 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:18:10 -0700 Subject: [PATCH 198/348] Update to Jackson 2.10.5.1 Closes gh-9236 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 91a26df99d..0f5ca9fae7 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -44,7 +44,7 @@ dependencyManagement { dependency 'ch.qos.logback:logback-core:1.2.3' dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.5' dependency 'com.fasterxml.jackson.core:jackson-core:2.10.5' - dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.5' + dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1' dependency 'com.fasterxml:classmate:1.3.4' dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1' dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion' From 04d382d8d803505fd1a96141101f189564438485 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:18:39 -0700 Subject: [PATCH 199/348] Update to Google App Engine 1.9.83 Closes gh-9237 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 36f90ff913..74f1146a2f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 -gaeVersion=1.9.82 +gaeVersion=1.9.83 springBootVersion=2.2.11.RELEASE version=5.2.8.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 7498ad95a11edf4a6652c536f376a4eabae95b4d Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:22:57 -0700 Subject: [PATCH 200/348] Update to Reactor Dysprosium-SR14 Closes gh-9238 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 0f5ca9fae7..cef5322a4d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR12' + ext.reactorVersion = 'Dysprosium-SR14' } if (!project.hasProperty('springVersion')) { From 5342133494f0aa1b08e648b45c169379e65d5b70 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:23:55 -0700 Subject: [PATCH 201/348] Update to RSocket 1.0.3 Closes gh-9239 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index cef5322a4d..2921d8390a 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR11' } -ext.rsocketVersion = '1.0.2' +ext.rsocketVersion = '1.0.3' dependencyManagement { imports { From e6b383a9c48370977b86bbeb105e151ec73222ea Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:25:52 -0700 Subject: [PATCH 202/348] Update to HttpComponents HttpClient 4.5.13 Closes gh-9240 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 2921d8390a..f89ad180c2 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -138,7 +138,7 @@ dependencyManagement { dependency 'org.apache.directory.shared:shared-cursor:0.9.15' dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15' dependency 'org.apache.directory.shared:shared-ldap:0.9.15' - dependency 'org.apache.httpcomponents:httpclient:4.5.12' + dependency 'org.apache.httpcomponents:httpclient:4.5.13' dependency 'org.apache.httpcomponents:httpcore:4.4.8' dependency 'org.apache.httpcomponents:httpmime:4.5.3' dependency 'org.apache.mina:mina-core:2.0.0-M6' From 5b60098548d671a8059f81a7fcadf9d76aa49f91 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:28:06 -0700 Subject: [PATCH 203/348] Update to Jetty 9.4.35 Closes gh-9241 --- gradle/dependency-management.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f89ad180c2..549029adbe 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -162,17 +162,17 @@ dependencyManagement { dependency 'org.codehaus.groovy:groovy-json:2.4.20' dependency 'org.codehaus.groovy:groovy:2.4.20' dependency 'org.eclipse.jdt:ecj:3.12.3' - dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.27.v20200227' - dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.27.v20200227' - dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-client:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-http:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-io:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-security:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-server:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-servlet:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-util:9.4.27.v20200227' - dependency 'org.eclipse.jetty:jetty-xml:9.4.27.v20200227' + dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.35.v20201120' + dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.35.v20201120' + dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-client:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-http:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-io:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-security:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-server:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-servlet:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-util:9.4.35.v20201120' + dependency 'org.eclipse.jetty:jetty-xml:9.4.35.v20201120' dependency 'org.eclipse.persistence:javax.persistence:2.2.1' dependency 'org.gebish:geb-ast:0.10.0' dependency 'org.gebish:geb-core:0.10.0' From f7b160d7a8f985b72da8668308825e386ba6fdb9 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:29:49 -0700 Subject: [PATCH 204/348] Update to Hibernate EntityManager 5.4.25 Closes gh-9242 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 549029adbe..61db5d81fe 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.22.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.25.Final' dependency 'org.hibernate:hibernate-validator:6.1.6.Final' dependency 'org.hsqldb:hsqldb:2.5.0' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From 664d9f1ba6550bbd7f69c1ca3eab26ae50fc3ec5 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:30:40 -0700 Subject: [PATCH 205/348] Update to HSQLDB 2.5.1 Closes gh-9243 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 61db5d81fe..f3b17b008c 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -184,7 +184,7 @@ dependencyManagement { dependency 'org.hibernate:hibernate-core:5.2.18.Final' dependency 'org.hibernate:hibernate-entitymanager:5.4.25.Final' dependency 'org.hibernate:hibernate-validator:6.1.6.Final' - dependency 'org.hsqldb:hsqldb:2.5.0' + dependency 'org.hsqldb:hsqldb:2.5.1' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.javassist:javassist:3.22.0-CR2' dependency 'org.jboss.logging:jboss-logging:3.3.1.Final' From 7cf1ca7ab1589d5ab9c9a316ab0b664f5856927f Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:31:33 -0700 Subject: [PATCH 206/348] Update to Powermock 2.0.9 Closes gh-9244 --- gradle/dependency-management.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f3b17b008c..f839ce1a96 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -24,12 +24,12 @@ dependencyManagement { dependency 'opensymphony:sitemesh:2.4.2' dependency 'org.gebish:geb-spock:0.10.0' dependency 'org.jasig.cas:cas-server-webapp:4.2.7' - dependency 'org.powermock:powermock-api-mockito2:2.0.7' - dependency 'org.powermock:powermock-api-support:2.0.7' - dependency 'org.powermock:powermock-core:2.0.7' - dependency 'org.powermock:powermock-module-junit4-common:2.0.7' - dependency 'org.powermock:powermock-module-junit4:2.0.7' - dependency 'org.powermock:powermock-reflect:2.0.7' + dependency 'org.powermock:powermock-api-mockito2:2.0.9' + dependency 'org.powermock:powermock-api-support:2.0.9' + dependency 'org.powermock:powermock-core:2.0.9' + dependency 'org.powermock:powermock-module-junit4-common:2.0.9' + dependency 'org.powermock:powermock-module-junit4:2.0.9' + dependency 'org.powermock:powermock-reflect:2.0.9' dependency 'org.python:jython:2.5.3' dependency 'org.spockframework:spock-core:1.0-groovy-2.4' dependency 'org.spockframework:spock-spring:1.0-groovy-2.4' From 9976eb9e3a2de8bd59e3552bc48ad7df5bbde546 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 15:32:58 -0700 Subject: [PATCH 207/348] Update to Spring LDAP Core 2.3.3 Closes gh-9245 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f839ce1a96..029ddaa274 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -209,7 +209,7 @@ dependencyManagement { dependency 'org.slf4j:slf4j-api:1.7.30' dependency 'org.slf4j:slf4j-nop:1.7.30' dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' - dependency 'org.springframework.ldap:spring-ldap-core:2.3.2.RELEASE' + dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE' dependency 'org.unbescape:unbescape:1.1.5.RELEASE' From d03d31c2e786e58d605f83682825039c5a6b36ee Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 16:06:32 -0700 Subject: [PATCH 208/348] Release 5.2.8.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 74f1146a2f..d009e89419 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.83 springBootVersion=2.2.11.RELEASE -version=5.2.8.BUILD-SNAPSHOT +version=5.2.8.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From c7b4e4a493cf6a906592538504801bcea57f4f0f Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 2 Dec 2020 19:09:04 -0700 Subject: [PATCH 209/348] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d009e89419..226f8a8ee1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.83 springBootVersion=2.2.11.RELEASE -version=5.2.8.RELEASE +version=5.2.9.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 174b71c017b4456d7bbadd10c0a4e0358e7145c6 Mon Sep 17 00:00:00 2001 From: Ovidiu Popa Date: Fri, 27 Nov 2020 09:10:54 +0200 Subject: [PATCH 210/348] OidcIdToken cannot be serialized to JSON if token contains claim of type JSONArray or JSONObject ObjectToListStringConverter and ObjectToMapStringObjectConverter were checking if the source object is of type List or Map and if the first element or key is a String. If we have a JSONArray containing Strings the above check will pass, meaning that a JSONArray will be returned which is not serializable (same applies to JSONObject) With this change, even if the check is passing a new List or Map will be returned. Closes gh-9210 --- .../ObjectToListStringConverter.java | 6 --- .../ObjectToMapStringObjectConverter.java | 3 -- .../ClaimConversionServiceTests.java | 45 ++++++++++++++++--- .../converter/ClaimTypeConverterTests.java | 23 +++++++--- 4 files changed, 56 insertions(+), 21 deletions(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java index daba913f25..ceea009f74 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToListStringConverter.java @@ -56,12 +56,6 @@ final class ObjectToListStringConverter implements ConditionalGenericConverter { if (source == null) { return null; } - if (source instanceof List) { - List sourceList = (List) source; - if (!sourceList.isEmpty() && sourceList.get(0) instanceof String) { - return source; - } - } if (source instanceof Collection) { Collection results = new ArrayList<>(); for (Object object : ((Collection) source)) { diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java index 6db09f9cf1..23874b71df 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ObjectToMapStringObjectConverter.java @@ -52,9 +52,6 @@ final class ObjectToMapStringObjectConverter implements ConditionalGenericConver return null; } Map sourceMap = (Map) source; - if (!sourceMap.isEmpty() && sourceMap.keySet().iterator().next() instanceof String) { - return source; - } Map result = new HashMap<>(); sourceMap.forEach((k, v) -> result.put(k.toString(), v)); return result; diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimConversionServiceTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimConversionServiceTests.java index c7b8cc3571..109c6e2b8e 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimConversionServiceTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimConversionServiceTests.java @@ -15,9 +15,10 @@ */ package org.springframework.security.oauth2.core.converter; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; import org.assertj.core.util.Lists; import org.junit.Test; -import org.springframework.core.convert.ConversionService; import java.net.URL; import java.time.Instant; @@ -29,6 +30,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.springframework.core.convert.ConversionService; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -141,9 +144,9 @@ public class ClaimConversionServiceTests { } @Test - public void convertCollectionStringWhenListStringThenReturnSame() { + public void convertCollectionStringWhenListStringThenReturnNotSameButEqual() { List list = Lists.list("1", "2", "3", "4"); - assertThat(this.conversionService.convert(list, Collection.class)).isSameAs(list); + assertThat(this.conversionService.convert(list, Collection.class)).isNotSameAs(list).isEqualTo(list); } @Test @@ -152,6 +155,17 @@ public class ClaimConversionServiceTests { .isEqualTo(Lists.list("1", "2", "3", "4")); } + @Test + public void convertListStringWhenJsonArrayThenConverts() { + JSONArray jsonArray = new JSONArray(); + jsonArray.add("1"); + jsonArray.add("2"); + jsonArray.add("3"); + jsonArray.add(null); + assertThat(this.conversionService.convert(jsonArray, List.class)).isNotInstanceOf(JSONArray.class) + .isEqualTo(Lists.list("1", "2", "3")); + } + @Test public void convertCollectionStringWhenNotConvertibleThenReturnSingletonList() { String string = "not-convertible-collection"; @@ -165,9 +179,9 @@ public class ClaimConversionServiceTests { } @Test - public void convertListStringWhenListStringThenReturnSame() { + public void convertListStringWhenListStringThenReturnNotSameButEqual() { List list = Lists.list("1", "2", "3", "4"); - assertThat(this.conversionService.convert(list, List.class)).isSameAs(list); + assertThat(this.conversionService.convert(list, List.class)).isNotSameAs(list).isEqualTo(list); } @Test @@ -189,7 +203,7 @@ public class ClaimConversionServiceTests { } @Test - public void convertMapStringObjectWhenMapStringObjectThenReturnSame() { + public void convertMapStringObjectWhenMapStringObjectThenReturnNotSameButEqual() { Map mapStringObject = new HashMap() { { put("key1", "value1"); @@ -197,7 +211,8 @@ public class ClaimConversionServiceTests { put("key3", "value3"); } }; - assertThat(this.conversionService.convert(mapStringObject, Map.class)).isSameAs(mapStringObject); + assertThat(this.conversionService.convert(mapStringObject, Map.class)).isNotSameAs(mapStringObject) + .isEqualTo(mapStringObject); } @Test @@ -219,6 +234,22 @@ public class ClaimConversionServiceTests { assertThat(this.conversionService.convert(mapIntegerObject, Map.class)).isEqualTo(mapStringObject); } + @Test + public void convertMapStringObjectWhenJsonObjectThenConverts() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("1", "value1"); + jsonObject.put("2", "value2"); + + Map mapStringObject = new HashMap() { + { + put("1", "value1"); + put("2", "value2"); + } + }; + assertThat(this.conversionService.convert(jsonObject, Map.class)).isNotInstanceOf(JSONObject.class) + .isEqualTo(mapStringObject); + } + @Test public void convertMapStringObjectWhenNotConvertibleThenReturnNull() { List notConvertibleList = Lists.list("1", "2", "3", "4"); diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverterTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverterTests.java index fee193f8e4..d95cb9b366 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverterTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverterTests.java @@ -15,7 +15,10 @@ */ package org.springframework.security.oauth2.core.converter; +import net.minidev.json.JSONArray; +import net.minidev.json.JSONObject; import org.assertj.core.util.Lists; +import org.assertj.core.util.Maps; import org.junit.Before; import org.junit.Test; import org.springframework.core.convert.TypeDescriptor; @@ -45,6 +48,8 @@ public class ClaimTypeConverterTests { private static final String COLLECTION_STRING_CLAIM = "collection-string-claim"; private static final String LIST_STRING_CLAIM = "list-string-claim"; private static final String MAP_STRING_OBJECT_CLAIM = "map-string-object-claim"; + private static final String JSON_ARRAY_CLAIM = "json-array-claim"; + private static final String JSON_OBJECT_CLAIM = "json-object-claim"; private ClaimTypeConverter claimTypeConverter; @Before @@ -107,7 +112,12 @@ public class ClaimTypeConverterTests { mapIntegerObject.put(1, "value1"); Map mapStringObject = new HashMap<>(); mapStringObject.put("1", "value1"); - + JSONArray jsonArray = new JSONArray(); + jsonArray.add("1"); + List jsonArrayListString = Lists.list("1"); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("1", "value1"); + Map jsonObjectMap = Maps.newHashMap("1", "value1"); Map claims = new HashMap<>(); claims.put(STRING_CLAIM, Boolean.TRUE); claims.put(BOOLEAN_CLAIM, "true"); @@ -116,7 +126,8 @@ public class ClaimTypeConverterTests { claims.put(COLLECTION_STRING_CLAIM, listNumber); claims.put(LIST_STRING_CLAIM, listNumber); claims.put(MAP_STRING_OBJECT_CLAIM, mapIntegerObject); - + claims.put(JSON_ARRAY_CLAIM, jsonArray); + claims.put(JSON_OBJECT_CLAIM, jsonObject); claims = this.claimTypeConverter.convert(claims); assertThat(claims.get(STRING_CLAIM)).isEqualTo("true"); @@ -126,6 +137,8 @@ public class ClaimTypeConverterTests { assertThat(claims.get(COLLECTION_STRING_CLAIM)).isEqualTo(listString); assertThat(claims.get(LIST_STRING_CLAIM)).isEqualTo(listString); assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isEqualTo(mapStringObject); + assertThat(claims.get(JSON_ARRAY_CLAIM)).isEqualTo(jsonArrayListString); + assertThat(claims.get(JSON_OBJECT_CLAIM)).isEqualTo(jsonObjectMap); } @Test @@ -153,9 +166,9 @@ public class ClaimTypeConverterTests { assertThat(claims.get(BOOLEAN_CLAIM)).isSameAs(bool); assertThat(claims.get(INSTANT_CLAIM)).isSameAs(instant); assertThat(claims.get(URL_CLAIM)).isSameAs(url); - assertThat(claims.get(COLLECTION_STRING_CLAIM)).isSameAs(listString); - assertThat(claims.get(LIST_STRING_CLAIM)).isSameAs(listString); - assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isSameAs(mapStringObject); + assertThat(claims.get(COLLECTION_STRING_CLAIM)).isNotSameAs(listString).isEqualTo(listString); + assertThat(claims.get(LIST_STRING_CLAIM)).isNotSameAs(listString).isEqualTo(listString); + assertThat(claims.get(MAP_STRING_OBJECT_CLAIM)).isNotSameAs(mapStringObject).isEqualTo(mapStringObject); } @Test From 6dc22835fd37979342e2f2ec19fc3c5a0fda725d Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 4 Jan 2021 12:06:53 -0700 Subject: [PATCH 211/348] Renew Sample Certificate Closes gh-9320 --- .../src/main/resources/certs/server.p12 | Bin 9319 -> 5255 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/samples/boot/webflux-x509/src/main/resources/certs/server.p12 b/samples/boot/webflux-x509/src/main/resources/certs/server.p12 index 8096cc73b9f82ff372925b81973f1e738917429c..ec536b2f6007eac9486c0daada114fe1faf4facd 100644 GIT binary patch delta 5232 zcmV-$6p!oYNQWswFoG0=0s#Xsf)qRk2`Yw2hW8Bt2LYgh6fFdT6e%!*6eTc%1!)Ee zDuzgg_YDCD0ic2fS_FaxRxpAEQZRxAPLV+;e{saMw#DZv(mn8P0sPbB(c^C#?LbaecLM{~i6>wsZBr@Sn$k8K4%A=cYc5w1 zf52ondaPawFg}aCzg`?j(YHMXMg}!GbqT&5KENoAXoEXTZ9r@D5U)$9kMq>(dotC? zM!|j;rd-aKBJ!kPijIrDJ2{+>X4VFHL_H{8MxGeI0ymLbb_45k>_QiMiE=(Bd5&An zVDTVKYNkLzbw#@kFX;r}U_vz-2Rjm(FdQ~dU3ZeG`<9zCs!^YXwMe_Kjh zbhb)ug~=ld+!$u|1`O6ChB50A^EE}axJVAD$WJyFY!j0aNrvp&hem;C_Gf__|17`# z0-w#-RM{vq)uae%$7&nix8=Y^*92(ji>9I6q70%zmALzb@veQm_<=CwbimZnFdjrC%1e-nTx8NV_$lwNiCdk1D+Y4MRpjkt6GGbwuAoduec zY|FWx@C5e)&p^p3AijsESy0?sOgitMd!JpT7*Z|v+Fk$>D{XmXk&%QexU6C&0-mU? z^Fyt`xW<{G9+6^4DSD&M7h<95_H(`)RCDy99`cq}G+M46bT9HYvO0byf5e;5+}I%o zBiy{lbziyo1K4{yM3wp>|AvX>-UPJJbH;ex3G8iyYV|oP`*@Y~e)bHbX3?D|5%dPN zA}z`3F&+F9NVb03qq}H%;%?svB4x)BnXUNAMoNYYwNByZe_$2TrEsIyy1F-_oFKviqdqM_#r_G)FPB7u5w%SU=b}+& zanFn)j)YKDY?b73Ugxd+DQt?5Xy1|eOAeBjyKs`5ZD-ZbS*FwVnWbUv-+HkNlh5v_ zUe7(3jkjFF;3Ys=ek_Zc=cSb&;_7{dFD=oQ9XvrX_3SFPoDV!Pf4;oY%jGo5lMEK~ zS4ZSxg*#mxs2W!%P5#!I0SjfjRA#=FfB=JDe+IaZExR*&Z@U2Ae5IfNtbcg-N{Jj) zvQA=jozEP90MD3e`v`9VyC}O#9P3kRi96{DxPYcZh@n{{%If=u2^HkGjw(xkb&25 z6ku~RlMyF!uuo=#64MK>m$O8SLLe?2LWH}c*t5S8CtsjaY8%pGBrT~Z{ZDTR>eyu3 ziRCZ+n}SIjS`Ncprctc-{pv=B=##{qGKH7Ct7SwcpF-kCf9l_;ZsNv|V3#fmtd0Kz zomCfQg9;)jF+8#}xL^;gJqkI-5mX9Yt+A5~jpR=oBj{Zsfl5nl#AN~VH#j#jGBr0hG%$h=v<3+(hDe6@4FLxRpn?viFoF)D0s#Op zf)1Jn2`Yw2hW8Bt2LUiC1_~;MNQU@m{E@PeLu9d^F?oRYd(YA6j?YhJ z*UQomFNXY+E}n5dTvp=6elYa-8-DPyuBf-G8YKPFY9ze1Actig0A>9PX+%YC$Gyz) zHS&7KM%uS_ypB4=mFyy z+g-;HE#k{7e{R@)jAPqj*2>Pp)CMB zb>ueO7Ut3gUEPtRp+t8HytHsFiftOnRD~2cDxmcmPwn;M=ng!|duVG8F0-J8@s~rh z)#Amn-fv4cN4vF$TrezB^oUmgh%jNHihv;l9h;=pe}lQ}oB@+(`og*j@gS3?tK2T| zJaRkl6pL^#aQMD?*xQOTOrP+TnqCR{Qj$a?)0P8Vcp=0<02Bhu0WVg3Ti7OYmlxAJ zTb(p}j9eE5XZj^pC3AshWqZZaGmNwjXb`MaLzb>O>6J8&GGMNowjBp|bbIdPcc>}x z*Vq%ye}UPW_(+Tqa}XoYG`nPK<>4axX|0r{wCyg@11W|Uj4EvsxaO}5iac@nGA3#a z@?^Ib3w?OZb&5PS9JxGec`CrSQqJMSP*^2^aYJ9aXvOd?b3|;ZuH;+d*lRG#qgeEJ=g2`udoT}1mvLE{b921BG9`O ze_EBmE)o5!hnh+xZF%rZP{83HBv#@52L0eS~deysRf!qEwM zX-xqq#T&rlg0c4{@N5L`Sh-DlEBmK^gJ3>Iq}DRUshOM~EdsrLENA!I-HYme>3&wk zS^&8PCztA);qW|IGY1Yh42DRxJ*#$Fe|Qxsid5|I#7VZsfr3~u)eCF`(*yfSiXhvg zSw~`J`DmQg4DHX^KF8)gw=p=uyLEeeu0e*=&} z29X1&kWE-vOg;XCBmD%4eG)3|RYfmC%N1L#t0J+6^t#`$1X^NI8 zRpg=4xj~r&+5a6={8KvnaHoVce>D#Mjdj2>tc?0}4q2Od@!27Lcl9S@xPfpnL_%dh ztPe%x%!5i&UaxS;OMV}ClgwM_k26$JjL#gq0avRiM}!|qk%%fIvF1Crl8YIL&yB`8 zrul#{OO7N{M2&LsnJuO{891!LW% z4X@nZW@9J?SFR{!Uy$fc!)ba4SY(UsnoB> zJ-thVI>Ga*Ia?5L2LfPmYsC*mVrl^l1uZubEbB1PXn}L&`^=u!8KfE9suC-csKX}W z(gatr{e5vNfBan2E*o)$ONE($W;6w5>v=aJiF}D$hL@MYgEvb2J7<*@y%gnKZTyqP z5GU+hJNp@4p_fu={m%=oVVcvIZmUZy(9E$t11vwMLBrQ;lbe>nESX*W*J{rlq(=f4PLkoOYsyhO?VOQw5@=d-jQq z!9$#xtk^e-lE(~EHn&AtV{@vEisX@*T8QcO$IWb*?bC%cC03Z(~Up=b~BLV2)=JHw++%Kf$eM*j}m(S&AJb z!t_lCe-CxVM+@^-)rvm1DNUeOOnljS*lE)$N?%18W40vM( zSujnk={4zy;lUH9UwEl&3_;Qx`{jet)Yd8mhOYrr+IFO*9H3{3aoiSxCkg(!+FIMy zXZ|S_E2~sZXK;n@QNb@3r}mefB_DiEM@l(Me=bs?6qKYwV+gO!5vq# z3A!>q-)y9qh}SfAH(+;l{soy~5Sobwf|iNID;^q1EX?7V!6~BMQw(p!M$_2Be@8iu zK*jfZ0l`)r#jvP3D3Bo!Xx*(_(wh8*viktmZH zJN;eGanH8tZiQWa&FioHL6Yq%YDq2jVxhD(vKray3>dKevuyI03`*R*XUpii)Wt_8&*M%3lS9aEpB_?@w4Pa}m96P3!eaBwY@k&PXlR1i zuB+Q4q9AbJdv4-+re#RtXV@mG*nwDYOcF|u06RJOE}8G=&}wa|EmPL5e>P!Xv4GDF z6s6eL$s+a+6+G%%7w2m>lBi=*^577_@vN2F9Al>}F%|Nl*=R5g$23qcB&6|v7yRtw zQ5~rJc>1G z9ljE_P%rmrH*i`Qy%(gBf1DI+V{B+4H6NClK_4Ruh>sFJv9vGrwAB;@ z3<W1i50Zi&K*tmlT>Rt(PoIo7{o3V48)hf1$Qr9 zz99tBO$>76gZ67ue~@jLOEZ|5u_u7nmtDs89+eM~$dK-v-S^`WkVQ*8<<$|A>!!U^ zx7$Yz3<(NaVDO490y4B`_d>JM*5+0H@yqEt*7=oTLji^u7)QRk)GaAq2|9rjWM~2C#fEjTuXGN zGYd!PRWHY|o+f^AeuC4QBAa0^Ap+1p6Zl8@(3gWnB^tUaAU#XoF;LN|$BG;R5l^8M z&69mZ81AKB1c2JO38Fw%~Fd;Ar1_dh)0|FWa00b0^dR&m`Z{kqQ qKey8ZA{U>2rom1Y1Qbs=E*|pY0mb3(+~5szVDyi~b=U#}0fwMxZL1Ol(^dV`AGjc6Mv`zu)(=wNKr3 zPIo{4>Qu79a-vn%d!r1kqjy+xds zgZh!P;0HiY)t8w4FtX9WI7eSkYsUVnSUHbw##wS@8HNcCI;I2z&CX%)ceRWMc6X9b z$>&vFa+B_Mwlq@iAHhk~5N9HamUGX(WqmNS*L#TIkeXN6wmI&rclyLfmE+hYWpI;` zQ3$4jx_*h4vY&X5xn5yWs?;`O89#Ip3kg?h5Fhgzr%!J{mVbR- z-l?FsIa*RYMjr+HclS{g;lU~b$UCUqnr%w|MxH5dXBp^UX8RIcZjSBf18B*JHXbGZ zJ81051c3_Ob7SMpK!)xutbBm7KRv5J@v5 ztOPiqxK1@Ak@WNe3Xz6%w;ljE(C3q{#wwa!y#5GZ3lv7Qiz73T;P5I(&r3y2VrtV* zn%>K}oqiIGeR`%py#6wTC^@o1?%WiKDcg(q>bWQQqMVJ7<+nO0a>%C#AM(y80%XX> z;y!%AH{T^ka8;mj5o>o`;ro(?WX(LrgG_3bMdJN6H%k%HVXts6@P-N4kJy)Zb=p}E z^R!|2sA}E&yEQ0sK)>6J{-K)NmnH_j2O<}KwGBEWj4l1r-ye_)qVP&kcEN2V=WX%|=fIeJg8D9B?p!1U} zRB~BbhqN)pDCXs3*LjOY-AEn=H#yOLBBW-*Q*C~km6bv4mUYDEr~6PH<65l~H{~l~ zjHcWY^7lX4{p%#b{@Hy&WJ(Ldz48^qHz0fm?QdQ-2)jOT40q?F$Mu|hP}*BdW(=9q zMv)f?5<-9E>Mn*+b)70TGzu}QAocNZdjy#laPsQNz)B`{QTL==v(%e{dlf4*bYzB5 ziZO|p8@X~wzi!KKq&M5Bc?*3s({oiC?l*OxPn#bQ9&&jzzC5kD&mOVX(z*Bhsqw_R zL=h|X0Q$jGJt&LgiTpZ3xxP49Q4M2gmIA(J4pxDbcJ^NfArF*w0^2(TisdILV#rje z72TJ0#C9ho6|IX-_L||1c>A7wp%PoPNj{Cjqtp=Df%bf*8$X$6`0ENHy__7A$V4f< z3lpG@YPwAAN#9s)kFy1D?owVvW7UM)QKBD%e^|;w_bPoZYDBbB&`*5u5F}9f$}ubH{!?vm22X>d}DRZXkUhT15mlX!Z}8` zENe#Dp|uP#jrMVLw8xV>Ig`udOI1U>Uq(HxM=zG&7=K(1Z~j2PuT~MVnn$fURsMcx{P}PbW2Aw|{DsE;c{PB-(A1`)HH628rdf zdVzGh@i{JFrNU2E!m0zp@DchS@(b`j3Ma0(nZlA8(dmY3^>;<=(rgs}K_wzKJ2ojM zgad>VggJ!iziRWZbAxdIuOm3@*f5xywm&S%*}1v6`Pq5d__#Uwx&Cog`Tvuv(co`d z(m8na)5CcCZa3#0O7S_9|KV!tr{{j}mu-JO+zYrX69${1i@78~l{(!0OK)jTw@}4^ z2Aq(T5EFFCHzD!3<)@%IXqB#FnAK>}Yk0H20sY4a)CN709D+kMG`Q8e3x$Ca5Z6yu zo9;>rZ?KovqwPR&2XY45)OA9QPJ!b9(yi?dhxsSR>69pA^8N^!Z9(-|dzmZ)NSin6 zMz{r(sDMXXeQ(fe4q2|2l%AQ2;)(C&mf3PNZ7v^^6HtqdjK%Ld3+%+tZi)6~>rMc0 z@WgKH$^FXS;g}moBQtc6kb9iV4>u1GaHj;DCMJndB*Kqwlx)0*rm5*NN__lyaJvaj z@dN24a|fM#%w1G0rc`|=^k`FVk5I)#gUjzpDqKw}qIT0Q!bA;+SBFUVZhK zK)$<#3EpWzg1+Fgc>Vf}5%E*l7&<1qHTjgRZZf~l4DSMRGG#eabwcK2W`Y~+(dfm1 zSQ})|yQg`9Ft8bWq+@%eQLZN_Fk0u*LcI7^ddYeGgfr|SM1v!9T0CL1pA^S%_CxTO zOkmq*vdT}HT0}-OD1z-xz9PsOb zV%o`Ro32&vro641;FIoD()L6&jqq4K$v$q=O*~O?j`LXBzHq{R+I>gK2#Aa}EqPA1 zzU)6oX~4!Y+JnG3I$jZJe$6hnqCYcXkuz z;7d5TXpe?Rk6jGaBmpZ1sY08)lOIRY5kwQAXvfu(t7mp>vs&wik_C*42ZTD-Zwpb4XqIb2I94xHI3# z2uTw<#BTy6)=Q!@_LP{;RLu)eIY#L#8 zBi3J3h)$P2b~ah>89+Wn&YiE@8XdPo+lMzDGK5$UnCit~p9t!pKFz4(+dm>scT6UT z1j^jh*uV1J`|5#;vFD*8fJF#Q9t~jyOSag4o-qt^qdVp%{`1od2G$%yQB&7Pw;dV6 zd=f@K!MME{87}49O6CLgxBIYpv3d#ri3gAFvPQXncnGCfs0HY%5(i@igXeJpE5=!9 z2|c9mfS38!0=6nvEzC?9$JT2h{W*c31huwO?Rj#0%;L91m`-g_WP2xwS(>VhyD~k# z2GhX$8PFT-`z!U|g%ovvHQRQ}%ga9ty<|*cAUcX{2ACgek$(B1_^v%G1ZR}=MM0F_ zILL`s;fH&-vSQMf>#Pb<*N9EMGGMcNbp}@k{!pu;hka#k@IwFgbxO?!$`)jv)=+gP zmvLpSorWWPuWJz{=fpROcMk$!!^$!wYH0hWlA0{gHI(p25!Luz361~Ik|p;_mYl+n zhb(&SYyBPUQ+|h$i8knPCRE1u^m#(|?kkiYk<-z|d(2P;3$+ghC3CLXo8l+?H#VaG z%2iZSOE$zYKrE$42&{;f8Ho`Rd zITOC$Z6XmAQ(-H{|E{nQS^X8G8JW!t41&==Aq!OsjI^)$%T#E?Kh%Mh&t|{%_?}5` z3tL-Ji6$N4b}dBBZLqoLYX-K;d6h=~GGmno5Vdq}yL|)x{SxIkV$w z^hfpc`=DKaD8@%>WP138(L;{VhZTfg=tAAHzHozW>##rk+FW!@S$5R9u_O~dO$pnn zd?41iHjzWOcL;+n7X+x$9o+Yn9J*5pdy=ZcuAX}|%M)zw8tc|r3($1hA1CYdwzC^) zdeQ!+SjP|_9tCcOLW9Y3$o83IHAT`JH^TaLUX`I&oO9;tMIhNtZ+UiarFHrhBR#5? zGKO7)$|;&~6HRm=z{xO5b&XiaY1uW>qik9?g)mEcX(mUam?znhI@H-{C2~PB1)lP= zsFhFY8Aqm~Hq_Xe&~)l+!dp1imWTr9C@b9OPRHwtQpX2Xz|JJp9C;Y`$zAVa+Bpexk<+hm_p zH@uFOdEaS-Xle{J<}-q(1mvP%)8jX2Lw#rLa28OW>)z`4SXP|P-=zvDv+jXwb-Je;Z<5*Bg?mX`9N8SVw)mX2v@t-@;%~Jpm1uIEDYy9J z$EoP1b2Ny5AW6`TX19a2T^#*N`@(@&fTNMLFcHPn6}( zOWWGo3lHVr@fQpLFF0qmSd<MtXPiaQ;Z}NV*>KNEh4w@A~RW?y7vU+5X z$(>n0GEn=A;W)ozC5GYWZV9zQQuAcZ^a{XlbrRmoMNh!_39?_M4u9$ly{^~|%g`1( zJ{}RO399*tuj+`f!*(vzy)#yZf5ST>i&O{4ZDd2GVx67=7hRoI60%I23{#IkL)15A z8jdB*<%4LZe1jHqU&lK}`g`h_!q)AfKL+@Y^ZjzbZU&fii(y0Fk@UoeMHvOBzvYa< z>u-RVn&>5gKQKFNQNVGWwcj|pHLFvSrUrtZXk0zmTge;t$QhmV#^%+e;lzxDqB30x z{@V~J9_rs!{_>(p*YoO!yqufy)H)OvH)bcgFe`?sw}xX-{=E+^)XGz{%iMjZJ>b=oJ&!Z~?)A_RQ<0km5%|Ar%PZ1$(T8iFEkA zHUfwnyy2durS4@ai(*nFpB>A;Jsz1?++2}G66;(CVXcmLTAwGzjB`x%V@DeqsMSCM z@oL>K!rXXULri$v=_Z^txvkWCxRo48Ba(J4JS9Gu5YBOUj0mOgIxViJJ6PksXO4)6 zhAb8DVP+}BD{sr3{ZKGhR@9waOoX=cnoCCK!)WSE^=uDx#wNu=g0kwq zWDmzIa=uL3&Us}X&y6C?ORGK9&RypwCL-T`(uN7ZX8BcE14p3MaG>$Zu55gF5yxAi ziT7|>!6t@aR`7N;u9Y2iu{_kUd#K$KCa2c|*H+XqpLsvIbtE6X<_uZPLLy(1(*M=acNox;)n^H@HlMkk+zsTR*Z5Xc(%{ z30)yY7Rs-_Nu+M)@b}(nLNN>lnX0ZxPGLXX@~4EEblm-F<-wY&=s&RpO_|wkU}RF? zrKiu~1wB&W)sCaAQG%R>HT%w#{?PAs`w+M|?r&m$dI=Q@{B00Ej3o=%iU!rdp5>MB z5)Hu4wLDY6LpedIAo)FcYV;V`GkF)>#qoX_bOzRP9`N$oSqkbqWKQ|@8r@h0oN5a+%laz=(-w!4KU23x8!6DjH^UaI1DlSN0s?;u;z`$zx~V^`SONu_GF zJP%WM-L;TZi>66ez{>{GHh1zBiP0W6`Yw-eGcaCypgNn~3g##1tp-7;&zhllQK_wT zr0USkWYa>S7KErBYtLXWry)MfwVO!Q55=BFFJpcUPmTwu@7rX+dr-?G`my~Kb-^9o zY5?tFE+R_%l{a3g@$naXgE|PDXGg;^Em_a0?rstE+b46dRpM!!-nCs^Yi z*K*gTud}MgE?;zEnED-v$z48pE5o?n`E53hOOeP;5%v~HP8JdOs1-W2R<+h?`Cz7n zp>t6>_qfu1y(n#6HR_X7<#FeZ8)s@S%$qYL!umf2p_lBRjR4NEt=z);Z)nu$FLnlF z4Vs!-_TI4LWU|%UVOG*@d!z3>Sic$_A2FL^nh-juKS(UEI|Vm!kCrtvktg#OS@a9@ z4vI>&MOFL-DVoUU=QgI@So3K`loMvg84$*9-?IYXg)+}^o#EWWZ9vnZxN4PwWEKT+ zmPrVbqt_zr9UT*MqZkuUV4g%8lbGwyrfaoz0N?9=eQ9eF_%0F`N~Cd5o$Dm{Xq~{w z?2fhd1O9kcXBb6OpU@ne#fGsniHR_b!ArZ29WXI~*);D!31zcj^vSoM0bHJ-3%HpA?ETknEbmQLB)ybkvC!u|g)tdcXw}@2_wJZ@k+fxU8Mw(`Wzv9Z}yZ!+F^)a_j|t;-(w z!sxlDxUz`wi;H3j-ypkR_|Bg?TEsIQQddcX41+vW#b ze-^fiLhPQcPzLTL% zpk4bJj`2={&7KTeTC(ibg$up&NUpxa8U3QXZl2exS)ve^SMjlJ&(&4SffUqS&3Mq=F`oOg2i%<+sV=Iu zZ$G_pEqMH}wc3`7Qwy}n_D-YJ=^}x^O%XejbVA}q8gc=epzmSDQ24oWY@N}u}o;Z_wXkP0Zc(^idA%fifL#l_wT+#)SUH3;Hg)TB$7%_tEnShKp*F- z>)K`rTS?6FwQwgUxWGSt1}~L$kdW<>o6KVnRzKEq<^uQ!4&^b zR;ZKIE|1U>!e027a5ZFbynnT%G-r9SL{&ze)SVU$Q>LCzDel>A-?m76)~(cr#}dOK z@M`9}-dNzWkm0efoE}}3IFh2wB1ImK@u!aL@{-l z`#Knz_tRk2MMz45}BgT$%UQbk=Nf3rPo(4ior!i%#ZnQAjP~%nbU( z%8B~A@YIHj%igT-qCFz|3JFp9Cm@Y(AwdMKDHC~(zmi+KZQkF6Mq{Az80aFo_>i60 z?SDC!s4x{HX{}|Aw?mFORxul4(RN^a|F+wB^Y1Swut)eCj<)7S^6uedb&9cBjwKh-sbZB^#O zMRwAU*$!huR|dR#H12pUP@lX_TkvST#*#^tg~E=ocok(P0K#UzPELzqY0vh#|Fo+X zx)hVK0P17Hg{J~!Kh}vlD*y<6H%8P`v@B*JETtTOQ*B@ZPFF&BsIbMg!CLF{&Nn!q ziy|{6*d3g`4V=H-W?&!T6v3t+Ak0#+DX8(Hj-$ESvZ5 z;pf})RWKalt{~V*=$0|^SC3N?Wm;0JNahnZ|E%QO50Na@7^@2c*vFebTePMwDCujb z%CbB1^lOXQpUg{Cr;K}+jhd8{DC{grpL1V;_9xzMmO0dBl8K+cyYhRSsVV5};iA5h z4u*!C2zX=f^tDJsR&7TVLTcT;={6WcBKqb{oKO|ZVU*BQ zIH|m|UWt-Xs(&qN$SKW4P_6gN448a7eokt>=IJ?15G+Z*AxlWYC6NEU+pqj6i!^{qc=WUTy1ewixq z66_6+Be^N_^^rb_y0+ZELF%l{25$+MORFX;50_{_CAtioRZ7m}vNu#Cr+WUr+YjQZ zaFlSmel*FIKgWbAho7{0mVR$`fT6WJR%^k90#=-ExRqOvP7W>~enP;A+fQ-YMT&t9 z_lK<~rdglUd0ie&FTJSxeoN3_mykKPK;_C7^}S` zXZop*>rcaJ9EmJTO<-?;0>KQr=gHtHE{ppFVgJ+}Hk{=H1iFH&D zXN=Pnmrz(hWQG^lnq-f!=@l)K6kIvM%@QMRfijwulQNQe+#06Q=BE&<@9ki}Nqo7VyTpkz z+vqt#jYjNc-pnIWaT30D%`KUBG7kyG?p1wQe{%$AhxVxp>k^7$H(fc#nfy2Wb)NyK zam#O$*Kc9SDu%clT!U*XvyH04F)^d=6gzjDb4wLbZ6DNI{8n$cD~v5^AIUucNXX?a zcmwp1R>silIoYS-ZDz&3`aDr-`s3>Wfl#oXh`hBU)PnJi%Q>=i1g863ygwyeFyZqG z{=flPHl97@E4AsEhfnR+%_7|lWD z@BCdbw@?=trk1=HuKT0%TA3D@xlxp(I3MS1cmfWn>ZxDRK1a#UMz@;R`c}X}#qxP} z9ADOZzi5tvHtt;)cPO*Nf6Ry6E_{<8;F`x*36>d@DKEVELP4yKJ0CJ@+`3Z{=$PF# zF>}MRq6AET7}5-Cww9=m*VIRLZ??k(e_)?Y47Xz)^GhZbavJu+5Y1a;x!>m$zI%3fAI~hSd!E{`LoW^H3TR+m=eWUBP#k06m8C{z38Q_CbTv? zKd`%jJRBPQxe~9dhRh|A3K*$8@s&F=$tj@yzVZATupSF(Os9O*I55`SMK-d4Z+_s*lxF_u_PF{=AY%YQ-Mdc#d7q*%9@x^x1u zM((wROrZF>X;wj8DGDj6b70f{7L2AZ&!-%VWbG}t5i+D6T98R zi8x36Bi1?3hnoBwU$;BiKBKQ&ftC)eBSF>cv4B9wcsS^btlTKC@uDH?!U7uWO zDehzq2obPl5o~l#_4~`1#~x$gk`PqZd0Y%cQ!05W_p8>XbX2xL>BO)C|R=I|RGoYt&YGt3Z!z+Yy^W zf$h)*3L*uUcuovmD$Y!7B5V|Fh;XossL)V^un;iVX{Cq9_t`s4Fbq8laK8i7%e0T# gV6bh)E>VivbvQeF9sIY$ZlEGvh5MnPA;a?j2l~y?GXMYp From 628ea00ad4141cf305140b3883b13e1f34c1ea13 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 12 Jan 2021 11:30:12 -0600 Subject: [PATCH 212/348] Fix CsrfWebFilter error message when expected CSRF not found Closes gh-9337 --- .../security/web/server/csrf/CsrfWebFilter.java | 2 +- .../security/web/server/csrf/CsrfWebFilterTests.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java index 64dcc1b6af..35cfe2a65a 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java @@ -133,7 +133,7 @@ public class CsrfWebFilter implements WebFilter { private Mono validateToken(ServerWebExchange exchange) { return this.csrfTokenRepository.loadToken(exchange) - .switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("CSRF Token has been associated to this client")))) + .switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("An expected CSRF token cannot be found")))) .filterWhen(expected -> containsValidCsrfToken(exchange, expected)) .switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token")))) .then(); diff --git a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java index 800f4806f7..617d61e1f6 100644 --- a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java @@ -65,8 +65,7 @@ public class CsrfWebFilterTests { private MockServerWebExchange get = from( MockServerHttpRequest.get("/")); - private ServerWebExchange post = from( - MockServerHttpRequest.post("/")); + private MockServerWebExchange post = MockServerWebExchange.from(MockServerHttpRequest.post("/")); @Test public void filterWhenGetThenSessionNotCreatedAndChainContinues() { @@ -110,6 +109,8 @@ public class CsrfWebFilterTests { .verifyComplete(); assertThat(this.post.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); + StepVerifier.create(this.post.getResponse().getBodyAsString()) + .assertNext(b -> assertThat(b).contains("An expected CSRF token cannot be found")); } @Test From 1181740f790c89a0286a17ee0f7fc474c07aadd3 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 17 Dec 2020 15:01:28 -0600 Subject: [PATCH 213/348] Constant Time Comparison for CSRF tokens Closes gh-9291 --- .../security/web/csrf/CsrfFilter.java | 109 ++++++++-------- .../web/server/csrf/CsrfWebFilter.java | 121 ++++++++++-------- 2 files changed, 124 insertions(+), 106 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java index 9a489c6f96..362168f109 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.security.web.csrf; import java.io.IOException; +import java.security.MessageDigest; import java.util.Arrays; import java.util.HashSet; @@ -28,6 +30,9 @@ import javax.servlet.http.HttpSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.log.LogMessage; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.crypto.codec.Utf8; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandlerImpl; import org.springframework.security.web.util.UrlUtils; @@ -35,8 +40,6 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.web.filter.OncePerRequestFilter; -import static java.lang.Boolean.TRUE; - /** *

* Applies @@ -58,6 +61,7 @@ import static java.lang.Boolean.TRUE; * @since 3.2 */ public final class CsrfFilter extends OncePerRequestFilter { + /** * The default {@link RequestMatcher} that indicates if CSRF protection is required or * not. The default is to ignore GET, HEAD, TRACE, OPTIONS and process all other @@ -66,18 +70,21 @@ public final class CsrfFilter extends OncePerRequestFilter { public static final RequestMatcher DEFAULT_CSRF_MATCHER = new DefaultRequiresCsrfMatcher(); /** - * The attribute name to use when marking a given request as one that should not be filtered. + * The attribute name to use when marking a given request as one that should not be + * filtered. * - * To use, set the attribute on your {@link HttpServletRequest}: - *

+	 * To use, set the attribute on your {@link HttpServletRequest}: 
 	 * 	CsrfFilter.skipRequest(request);
 	 * 
*/ private static final String SHOULD_NOT_FILTER = "SHOULD_NOT_FILTER" + CsrfFilter.class.getName(); private final Log logger = LogFactory.getLog(getClass()); + private final CsrfTokenRepository tokenRepository; + private RequestMatcher requireCsrfProtectionMatcher = DEFAULT_CSRF_MATCHER; + private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); public CsrfFilter(CsrfTokenRepository csrfTokenRepository) { @@ -87,62 +94,46 @@ public final class CsrfFilter extends OncePerRequestFilter { @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - return TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER)); + return Boolean.TRUE.equals(request.getAttribute(SHOULD_NOT_FILTER)); } - /* - * (non-Javadoc) - * - * @see - * org.springframework.web.filter.OncePerRequestFilter#doFilterInternal(javax.servlet - * .http.HttpServletRequest, javax.servlet.http.HttpServletResponse, - * javax.servlet.FilterChain) - */ @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { request.setAttribute(HttpServletResponse.class.getName(), response); - CsrfToken csrfToken = this.tokenRepository.loadToken(request); - final boolean missingToken = csrfToken == null; + boolean missingToken = (csrfToken == null); if (missingToken) { csrfToken = this.tokenRepository.generateToken(request); this.tokenRepository.saveToken(csrfToken, request, response); } request.setAttribute(CsrfToken.class.getName(), csrfToken); request.setAttribute(csrfToken.getParameterName(), csrfToken); - if (!this.requireCsrfProtectionMatcher.matches(request)) { + if (this.logger.isTraceEnabled()) { + this.logger.trace("Did not protect against CSRF since request did not match " + + this.requireCsrfProtectionMatcher); + } filterChain.doFilter(request, response); return; } - String actualToken = request.getHeader(csrfToken.getHeaderName()); if (actualToken == null) { actualToken = request.getParameter(csrfToken.getParameterName()); } - if (!csrfToken.getToken().equals(actualToken)) { - if (this.logger.isDebugEnabled()) { - this.logger.debug("Invalid CSRF token found for " - + UrlUtils.buildFullRequestUrl(request)); - } - if (missingToken) { - this.accessDeniedHandler.handle(request, response, - new MissingCsrfTokenException(actualToken)); - } - else { - this.accessDeniedHandler.handle(request, response, - new InvalidCsrfTokenException(csrfToken, actualToken)); - } + if (!equalsConstantTime(csrfToken.getToken(), actualToken)) { + this.logger.debug( + LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request))); + AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken) + : new MissingCsrfTokenException(actualToken); + this.accessDeniedHandler.handle(request, response, exception); return; } - filterChain.doFilter(request, response); } public static void skipRequest(HttpServletRequest request) { - request.setAttribute(SHOULD_NOT_FILTER, TRUE); + request.setAttribute(SHOULD_NOT_FILTER, Boolean.TRUE); } /** @@ -154,14 +145,11 @@ public final class CsrfFilter extends OncePerRequestFilter { * The default is to apply CSRF protection for any HTTP method other than GET, HEAD, * TRACE, OPTIONS. *

- * * @param requireCsrfProtectionMatcher the {@link RequestMatcher} used to determine if * CSRF protection should be applied. */ - public void setRequireCsrfProtectionMatcher( - RequestMatcher requireCsrfProtectionMatcher) { - Assert.notNull(requireCsrfProtectionMatcher, - "requireCsrfProtectionMatcher cannot be null"); + public void setRequireCsrfProtectionMatcher(RequestMatcher requireCsrfProtectionMatcher) { + Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null"); this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher; } @@ -172,7 +160,6 @@ public final class CsrfFilter extends OncePerRequestFilter { *

* The default is to use AccessDeniedHandlerImpl with no arguments. *

- * * @param accessDeniedHandler the {@link AccessDeniedHandler} to use */ public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { @@ -180,20 +167,38 @@ public final class CsrfFilter extends OncePerRequestFilter { this.accessDeniedHandler = accessDeniedHandler; } - private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { - private final HashSet allowedMethods = new HashSet<>( - Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS")); + /** + * Constant time comparison to prevent against timing attacks. + * @param expected + * @param actual + * @return + */ + private static boolean equalsConstantTime(String expected, String actual) { + byte[] expectedBytes = bytesUtf8(expected); + byte[] actualBytes = bytesUtf8(actual); + return MessageDigest.isEqual(expectedBytes, actualBytes); + } + + private static byte[] bytesUtf8(String s) { + // need to check if Utf8.encode() runs in constant time (probably not). + // This may leak length of string. + return (s != null) ? Utf8.encode(s) : null; + } + + private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { + + private final HashSet allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS")); - /* - * (non-Javadoc) - * - * @see - * org.springframework.security.web.util.matcher.RequestMatcher#matches(javax. - * servlet.http.HttpServletRequest) - */ @Override public boolean matches(HttpServletRequest request) { return !this.allowedMethods.contains(request.getMethod()); } + + @Override + public String toString() { + return "CsrfNotRequired " + this.allowedMethods; + } + } + } diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java index 35cfe2a65a..a2699018b3 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java @@ -16,26 +16,28 @@ package org.springframework.security.web.server.csrf; +import java.security.MessageDigest; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import reactor.core.publisher.Mono; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FormFieldPart; import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.crypto.codec.Utf8; import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Mono; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import static java.lang.Boolean.TRUE; /** *

@@ -64,13 +66,14 @@ import static java.lang.Boolean.TRUE; * @since 5.0 */ public class CsrfWebFilter implements WebFilter { + public static final ServerWebExchangeMatcher DEFAULT_CSRF_MATCHER = new DefaultRequireCsrfProtectionMatcher(); /** - * The attribute name to use when marking a given request as one that should not be filtered. + * The attribute name to use when marking a given request as one that should not be + * filtered. * - * To use, set the attribute on your {@link ServerWebExchange}: - *

+	 * To use, set the attribute on your {@link ServerWebExchange}: 
 	 * 	CsrfWebFilter.skipExchange(exchange);
 	 * 
*/ @@ -80,32 +83,31 @@ public class CsrfWebFilter implements WebFilter { private ServerCsrfTokenRepository csrfTokenRepository = new WebSessionServerCsrfTokenRepository(); - private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(HttpStatus.FORBIDDEN); + private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler( + HttpStatus.FORBIDDEN); private boolean isTokenFromMultipartDataEnabled; - public void setAccessDeniedHandler( - ServerAccessDeniedHandler accessDeniedHandler) { + public void setAccessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) { Assert.notNull(accessDeniedHandler, "accessDeniedHandler"); this.accessDeniedHandler = accessDeniedHandler; } - public void setCsrfTokenRepository( - ServerCsrfTokenRepository csrfTokenRepository) { + public void setCsrfTokenRepository(ServerCsrfTokenRepository csrfTokenRepository) { Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null"); this.csrfTokenRepository = csrfTokenRepository; } - public void setRequireCsrfProtectionMatcher( - ServerWebExchangeMatcher requireCsrfProtectionMatcher) { + public void setRequireCsrfProtectionMatcher(ServerWebExchangeMatcher requireCsrfProtectionMatcher) { Assert.notNull(requireCsrfProtectionMatcher, "requireCsrfProtectionMatcher cannot be null"); this.requireCsrfProtectionMatcher = requireCsrfProtectionMatcher; } /** - * Specifies if the {@code CsrfWebFilter} should try to resolve the actual CSRF token from the body of multipart - * data requests. - * @param tokenFromMultipartDataEnabled true if should read from multipart form body, else false. Default is false + * Specifies if the {@code CsrfWebFilter} should try to resolve the actual CSRF token + * from the body of multipart data requests. + * @param tokenFromMultipartDataEnabled true if should read from multipart form body, + * else false. Default is false */ public void setTokenFromMultipartDataEnabled(boolean tokenFromMultipartDataEnabled) { this.isTokenFromMultipartDataEnabled = tokenFromMultipartDataEnabled; @@ -113,38 +115,33 @@ public class CsrfWebFilter implements WebFilter { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - if (TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER))) { + if (Boolean.TRUE.equals(exchange.getAttribute(SHOULD_NOT_FILTER))) { return chain.filter(exchange).then(Mono.empty()); } - - return this.requireCsrfProtectionMatcher.matches(exchange) - .filter( matchResult -> matchResult.isMatch()) - .filter( matchResult -> !exchange.getAttributes().containsKey(CsrfToken.class.getName())) - .flatMap(m -> validateToken(exchange)) - .flatMap(m -> continueFilterChain(exchange, chain)) - .switchIfEmpty(continueFilterChain(exchange, chain).then(Mono.empty())) - .onErrorResume(CsrfException.class, e -> this.accessDeniedHandler - .handle(exchange, e)); + return this.requireCsrfProtectionMatcher.matches(exchange).filter(MatchResult::isMatch) + .filter((matchResult) -> !exchange.getAttributes().containsKey(CsrfToken.class.getName())) + .flatMap((m) -> validateToken(exchange)).flatMap((m) -> continueFilterChain(exchange, chain)) + .switchIfEmpty(continueFilterChain(exchange, chain).then(Mono.empty())) + .onErrorResume(CsrfException.class, (ex) -> this.accessDeniedHandler.handle(exchange, ex)); } public static void skipExchange(ServerWebExchange exchange) { - exchange.getAttributes().put(SHOULD_NOT_FILTER, TRUE); + exchange.getAttributes().put(SHOULD_NOT_FILTER, Boolean.TRUE); } private Mono validateToken(ServerWebExchange exchange) { return this.csrfTokenRepository.loadToken(exchange) - .switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("An expected CSRF token cannot be found")))) - .filterWhen(expected -> containsValidCsrfToken(exchange, expected)) - .switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token")))) - .then(); + .switchIfEmpty( + Mono.defer(() -> Mono.error(new CsrfException("An expected CSRF token cannot be found")))) + .filterWhen((expected) -> containsValidCsrfToken(exchange, expected)) + .switchIfEmpty(Mono.defer(() -> Mono.error(new CsrfException("Invalid CSRF Token")))).then(); } private Mono containsValidCsrfToken(ServerWebExchange exchange, CsrfToken expected) { - return exchange.getFormData() - .flatMap(data -> Mono.justOrEmpty(data.getFirst(expected.getParameterName()))) - .switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName()))) - .switchIfEmpty(tokenFromMultipartData(exchange, expected)) - .map(actual -> actual.equals(expected.getToken())); + return exchange.getFormData().flatMap((data) -> Mono.justOrEmpty(data.getFirst(expected.getParameterName()))) + .switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(expected.getHeaderName()))) + .switchIfEmpty(tokenFromMultipartData(exchange, expected)) + .map((actual) -> equalsConstantTime(actual, expected.getToken())); } private Mono tokenFromMultipartData(ServerWebExchange exchange, CsrfToken expected) { @@ -157,14 +154,12 @@ public class CsrfWebFilter implements WebFilter { if (!contentType.includes(MediaType.MULTIPART_FORM_DATA)) { return Mono.empty(); } - return exchange.getMultipartData() - .map(d -> d.getFirst(expected.getParameterName())) - .cast(FormFieldPart.class) - .map(FormFieldPart::value); + return exchange.getMultipartData().map((d) -> d.getFirst(expected.getParameterName())).cast(FormFieldPart.class) + .map(FormFieldPart::value); } private Mono continueFilterChain(ServerWebExchange exchange, WebFilterChain chain) { - return Mono.defer(() ->{ + return Mono.defer(() -> { Mono csrfToken = csrfToken(exchange); exchange.getAttributes().put(CsrfToken.class.getName(), csrfToken); return chain.filter(exchange); @@ -172,26 +167,44 @@ public class CsrfWebFilter implements WebFilter { } private Mono csrfToken(ServerWebExchange exchange) { - return this.csrfTokenRepository.loadToken(exchange) - .switchIfEmpty(generateToken(exchange)); + return this.csrfTokenRepository.loadToken(exchange).switchIfEmpty(generateToken(exchange)); + } + + /** + * Constant time comparison to prevent against timing attacks. + * @param expected + * @param actual + * @return + */ + private static boolean equalsConstantTime(String expected, String actual) { + byte[] expectedBytes = bytesUtf8(expected); + byte[] actualBytes = bytesUtf8(actual); + return MessageDigest.isEqual(expectedBytes, actualBytes); + } + + private static byte[] bytesUtf8(String s) { + // need to check if Utf8.encode() runs in constant time (probably not). + // This may leak length of string. + return (s != null) ? Utf8.encode(s) : null; } private Mono generateToken(ServerWebExchange exchange) { return this.csrfTokenRepository.generateToken(exchange) - .delayUntil(token -> this.csrfTokenRepository.saveToken(exchange, token)); + .delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token)); } private static class DefaultRequireCsrfProtectionMatcher implements ServerWebExchangeMatcher { + private static final Set ALLOWED_METHODS = new HashSet<>( - Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS)); + Arrays.asList(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.TRACE, HttpMethod.OPTIONS)); @Override public Mono matches(ServerWebExchange exchange) { - return Mono.just(exchange.getRequest()) - .flatMap(r -> Mono.justOrEmpty(r.getMethod())) - .filter(m -> ALLOWED_METHODS.contains(m)) - .flatMap(m -> MatchResult.notMatch()) - .switchIfEmpty(MatchResult.match()); + return Mono.just(exchange.getRequest()).flatMap((r) -> Mono.justOrEmpty(r.getMethod())) + .filter(ALLOWED_METHODS::contains).flatMap((m) -> MatchResult.notMatch()) + .switchIfEmpty(MatchResult.match()); } + } + } From 57dfbeecbb6c13bfbfbd405cd196b6eeea7e763a Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 22 Jan 2021 14:33:02 +0100 Subject: [PATCH 214/348] Provide artifactoryUsername/Password in docs and schema jobs --- Jenkinsfile | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4c694f5de3..a7ee4c2be3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -174,8 +174,10 @@ try { checkout scm sh "git clean -dfx" withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { + sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + } } } } @@ -187,8 +189,10 @@ try { checkout scm sh "git clean -dfx" withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + withCredentials([ARTIFACTORY_CREDENTIALS]) { + withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { + sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + } } } } From 32acb04efe821e803541159af69e8a185df94f1d Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 22 Jan 2021 11:18:12 -0700 Subject: [PATCH 215/348] Fix SAML 2.0 Javaconfig Sample Issue gh-9362 --- ...rity-samples-javaconfig-saml2-login.gradle | 1 + ...sageSecurityWebApplicationInitializer.java | 4 ++++ .../samples/config/SecurityConfig.java | 19 ++++++++----------- .../saml2login/src/main/resources/logback.xml | 12 ++++++++++++ .../samples/config/SecurityConfigTests.java | 4 ++-- 5 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 samples/javaconfig/saml2login/src/main/resources/logback.xml diff --git a/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle b/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle index baa1385e4c..3ca4ac602d 100644 --- a/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle +++ b/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle @@ -5,6 +5,7 @@ dependencies { compile project(':spring-security-config') compile "org.bouncycastle:bcprov-jdk15on" compile "org.bouncycastle:bcpkix-jdk15on" + compile slf4jDependencies testCompile project(':spring-security-test') } diff --git a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java index 7de3308deb..2ad78bd231 100644 --- a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java +++ b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/MessageSecurityWebApplicationInitializer.java @@ -27,6 +27,10 @@ import org.springframework.security.web.session.HttpSessionEventPublisher; public class MessageSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { + public MessageSecurityWebApplicationInitializer() { + super(SecurityConfig.class); + } + @Override protected boolean enableHttpSessionEventPublisher() { return true; diff --git a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index ccf4154e27..9351902f11 100644 --- a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -15,6 +15,8 @@ */ package org.springframework.security.samples.config; + +import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -23,6 +25,7 @@ import org.springframework.security.converter.RsaKeyConverters; import org.springframework.security.saml2.credentials.Saml2X509Credential; import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter; import java.io.ByteArrayInputStream; @@ -39,7 +42,8 @@ import static org.springframework.security.saml2.credentials.Saml2X509Credential @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { - RelyingPartyRegistration getSaml2AuthenticationConfiguration() throws Exception { + @Bean + RelyingPartyRegistrationRepository getSaml2AuthenticationConfiguration() throws Exception { //remote IDP entity ID String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"; //remote WebSSO Endpoint - Where to Send AuthNRequests to @@ -53,14 +57,14 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { //IDP certificate for verification of incoming messages Saml2X509Credential idpVerificationCertificate = getVerificationCertificate(); String acsUrlTemplate = "{baseUrl}" + Saml2WebSsoAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI; - return RelyingPartyRegistration.withRegistrationId(registrationId) + return new InMemoryRelyingPartyRegistrationRepository(RelyingPartyRegistration.withRegistrationId(registrationId) .remoteIdpEntityId(idpEntityId) .idpWebSsoUrl(webSsoEndpoint) .credentials(c -> c.add(signingCredential)) .credentials(c -> c.add(idpVerificationCertificate)) .localEntityIdTemplate(localEntityIdTemplate) .assertionConsumerServiceUrlTemplate(acsUrlTemplate) - .build(); + .build()); } @Override @@ -70,14 +74,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { .authorizeRequests() .anyRequest().authenticated() .and() - .saml2Login() - .relyingPartyRegistrationRepository( - new InMemoryRelyingPartyRegistrationRepository( - getSaml2AuthenticationConfiguration() - ) - ) - .loginProcessingUrl("/sample/jc/saml2/sso/{registrationId}") - ; + .saml2Login(); // @formatter:on } diff --git a/samples/javaconfig/saml2login/src/main/resources/logback.xml b/samples/javaconfig/saml2login/src/main/resources/logback.xml new file mode 100644 index 0000000000..3ebbcc0ddd --- /dev/null +++ b/samples/javaconfig/saml2login/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java b/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java index 3943eb91e0..62e2bd3529 100644 --- a/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java +++ b/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java @@ -44,7 +44,7 @@ public class SecurityConfigTests { public void filterWhenLoginProcessingUrlIsSetInJavaConfigThenTheFilterHasIt() { FilterChainProxy filterChain = context.getBean(FilterChainProxy.class); Assert.assertNotNull(filterChain); - final List filters = filterChain.getFilters("/sample/jc/saml2/sso/test-id"); + final List filters = filterChain.getFilters("/login/saml2/sso/one"); Assert.assertNotNull(filters); Saml2WebSsoAuthenticationFilter filter = (Saml2WebSsoAuthenticationFilter) filters .stream() @@ -55,6 +55,6 @@ public class SecurityConfigTests { .get(); final Object matcher = ReflectionTestUtils.getField(filter, "requiresAuthenticationRequestMatcher"); final Object pattern = ReflectionTestUtils.getField(matcher, "pattern"); - Assert.assertEquals("loginProcessingUrl mismatch", "/sample/jc/saml2/sso/{registrationId}", pattern); + Assert.assertEquals("loginProcessingUrl mismatch", "/login/saml2/sso/{registrationId}", pattern); } } From 6df5dc4ecf039bf222eb0876983c8cc1640cb640 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 21 Jan 2021 09:13:30 -0700 Subject: [PATCH 216/348] Migrate SAML 2.0 Samples to PCFOne Closes gh-9362 --- .../security/samples/Saml2LoginIntegrationTests.java | 12 ++++++------ .../saml2login/src/main/resources/application.yml | 4 ++-- .../security/samples/config/SecurityConfig.java | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java index a401ce8a4f..2bb2dbb568 100644 --- a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java +++ b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java @@ -124,7 +124,7 @@ public class Saml2LoginIntegrationTests { public void authenticateRequestWhenUnauthenticatedThenRespondsWithRedirectAuthNRequestXML() throws Exception { mockMvc.perform(get("http://localhost:8080/saml2/authenticate/simplesamlphp")) .andExpect(status().is3xxRedirection()) - .andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest="))); + .andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php?SAMLRequest="))); } @Test @@ -134,7 +134,7 @@ public class Saml2LoginIntegrationTests { .param("RelayState", "relay state value with spaces") ) .andExpect(status().is3xxRedirection()) - .andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php?SAMLRequest="))) + .andExpect(header().string("Location", startsWith("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php?SAMLRequest="))) .andExpect(header().string("Location", containsString("RelayState=relay%20state%20value%20with%20spaces"))); } @@ -155,7 +155,7 @@ public class Saml2LoginIntegrationTests { String destination = authnRequest.getDestination(); assertEquals( "Destination must match", - "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php", + "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php", destination ); String acsURL = authnRequest.getAssertionConsumerServiceURL(); @@ -317,14 +317,14 @@ public class Saml2LoginIntegrationTests { Response response = OpenSamlActionTestingSupport.buildResponse(); response.setID("_" + UUID.randomUUID().toString()); response.setDestination("http://localhost:8080/login/saml2/sso/simplesamlphp"); - response.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php")); + response.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php")); return response; } private Assertion buildAssertion(String username) { Assertion assertion = OpenSamlActionTestingSupport.buildAssertion(); assertion.setIssueInstant(DateTime.now()); - assertion.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php")); + assertion.setIssuer(buildIssuer("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php")); assertion.setSubject(buildSubject(username)); assertion.setConditions(buildConditions()); @@ -346,7 +346,7 @@ public class Saml2LoginIntegrationTests { final PrivateKey privateKey = KeySupport.decodePrivateKey(key.getBytes(UTF_8), new char[0]); BasicCredential cred = CredentialSupport.getSimpleCredential(publicKey, privateKey); cred.setUsageType(usageType); - cred.setEntityId("https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"); + cred.setEntityId("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); return cred; } diff --git a/samples/boot/saml2login/src/main/resources/application.yml b/samples/boot/saml2login/src/main/resources/application.yml index c8cbdd45ce..61de74192c 100644 --- a/samples/boot/saml2login/src/main/resources/application.yml +++ b/samples/boot/saml2login/src/main/resources/application.yml @@ -12,5 +12,5 @@ spring: verification: credentials: - certificate-location: "classpath:credentials/idp-certificate.crt" - entity-id: https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php - sso-url: https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php + entity-id: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php + sso-url: https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php diff --git a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index 9351902f11..13ff9f674f 100644 --- a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -45,9 +45,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean RelyingPartyRegistrationRepository getSaml2AuthenticationConfiguration() throws Exception { //remote IDP entity ID - String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"; + String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"; //remote WebSSO Endpoint - Where to Send AuthNRequests to - String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"; + String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"; //local registration ID String registrationId = "simplesamlphp"; //local entity ID - autogenerated based on URL From 52ad49074d335ce5d20dcd2dde8e7578254d9739 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 25 Jan 2021 08:32:17 -0700 Subject: [PATCH 217/348] Migrate SAML 2.0 Tests and Docs to PCFOne Issue gh-9362 --- .../config/annotation/web/builders/HttpSecurity.java | 8 ++++---- .../asciidoc/_includes/servlet/saml2/saml2-login.adoc | 4 ++-- .../service/registration/RelyingPartyRegistration.java | 4 ++-- .../saml2login/src/main/resources/templates/index.html | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 45e8568872..bce40f4cd7 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -1933,9 +1933,9 @@ public final class HttpSecurity extends * * private RelyingPartyRegistration getSaml2RelyingPartyRegistration() { * //remote IDP entity ID - * String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"; + * String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"; * //remote WebSSO Endpoint - Where to Send AuthNRequests to - * String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"; + * String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"; * //local registration ID * String registrationId = "simplesamlphp"; * //local entity ID - autogenerated based on URL @@ -2024,9 +2024,9 @@ public final class HttpSecurity extends * * private RelyingPartyRegistration getSaml2RelyingPartyRegistration() { * //remote IDP entity ID - * String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php"; + * String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"; * //remote WebSSO Endpoint - Where to Send AuthNRequests to - * String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php"; + * String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"; * //local registration ID * String registrationId = "simplesamlphp"; * //local entity ID - autogenerated based on URL diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc index 3dad68482c..c32252cb82 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/saml2/saml2-login.adoc @@ -270,9 +270,9 @@ spring: saml2: login: relying-parties: - - entity-id: &idp-entity-id https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php + - entity-id: &idp-entity-id https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php registration-id: simplesamlphp - web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php + web-sso-url: &idp-sso-url https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php signing-credentials: &service-provider-credentials - private-key: | -----BEGIN PRIVATE KEY----- diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java index c0327ab104..722ae46ccc 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistration.java @@ -37,9 +37,9 @@ import static org.springframework.util.Assert.notNull; * A fully configured registration may look like *
  *		//remote IDP entity ID
- *		String idpEntityId = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/metadata.php";
+ *		String idpEntityId = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php";
  *		//remote WebSSO Endpoint - Where to Send AuthNRequests to
- *		String webSsoEndpoint = "https://simplesaml-for-spring-saml.cfapps.io/saml2/idp/SSOService.php";
+ *		String webSsoEndpoint = "https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php";
  *		//local registration ID
  *		String registrationId = "simplesamlphp";
  *		//local entity ID - autogenerated based on URL
diff --git a/samples/boot/saml2login/src/main/resources/templates/index.html b/samples/boot/saml2login/src/main/resources/templates/index.html
index 5251b3a8e9..54dd471571 100644
--- a/samples/boot/saml2login/src/main/resources/templates/index.html
+++ b/samples/boot/saml2login/src/main/resources/templates/index.html
@@ -28,7 +28,7 @@
             Log Out
         
         
  • - + Log out of SimpleSAMLPhp
  • From 98399c920a834d9aef30fdcdd8ebe2840d37c91b Mon Sep 17 00:00:00 2001 From: Benjamin Faal Date: Tue, 12 Jan 2021 15:55:28 +0100 Subject: [PATCH 218/348] Make user info response status check error only Closes gh-9336 --- .../DefaultReactiveOAuth2UserService.java | 16 ++++++-------- ...DefaultReactiveOAuth2UserServiceTests.java | 22 +++++++++++++++++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java index 71a0fee913..5baff07bef 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.security.oauth2.client.userinfo; @@ -22,6 +21,11 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import com.nimbusds.oauth2.sdk.ErrorObject; +import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse; +import net.minidev.json.JSONObject; +import reactor.core.publisher.Mono; + import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -41,12 +45,6 @@ import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; -import com.nimbusds.oauth2.sdk.ErrorObject; -import com.nimbusds.openid.connect.sdk.UserInfoErrorResponse; - -import net.minidev.json.JSONObject; -import reactor.core.publisher.Mono; - /** * An implementation of an {@link ReactiveOAuth2UserService} that supports standard OAuth 2.0 Provider's. *

    @@ -119,7 +117,7 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi } Mono> userAttributes = requestHeadersSpec .retrieve() - .onStatus(s -> s != HttpStatus.OK, response -> parse(response).map(userInfoErrorResponse -> { + .onStatus(HttpStatus::isError, response -> parse(response).map(userInfoErrorResponse -> { String description = userInfoErrorResponse.getErrorObject().getDescription(); OAuth2Error oauth2Error = new OAuth2Error( INVALID_USER_INFO_RESPONSE_ERROR_CODE, description, diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java index 812ce4e434..fd89bb0428 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.security.oauth2.client.userinfo; import java.time.Duration; @@ -50,6 +49,7 @@ import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; import org.springframework.web.reactive.function.client.WebClient; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -152,6 +152,24 @@ public class DefaultReactiveOAuth2UserServiceTests { assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); } + // gh-9336 + @Test + public void loadUserWhenUserInfo201CreatedResponseThenReturnUser() { + // @formatter:off + String userInfoResponse = "{\n" + + " \"id\": \"user1\",\n" + + " \"first-name\": \"first\",\n" + + " \"last-name\": \"last\",\n" + + " \"middle-name\": \"middle\",\n" + + " \"address\": \"address\",\n" + + " \"email\": \"user1@example.com\"\n" + + "}\n"; + // @formatter:on + this.server.enqueue(new MockResponse().setResponseCode(201) + .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(userInfoResponse)); + assertThatCode(() -> this.userService.loadUser(oauth2UserRequest()).block()).doesNotThrowAnyException(); + } + // gh-5500 @Test public void loadUserWhenAuthenticationMethodHeaderSuccessResponseThenHttpMethodGet() throws Exception { From 4de2dbb4cd0b687cf1d4519647801b8796f71c05 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 26 Jan 2021 11:11:05 -0600 Subject: [PATCH 219/348] Update to spring-build-conventions:0.0.23.1.RELEASE Fixes use of repo.spring.io --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 25457ad21f..bd7f59749b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.23.RELEASE' + classpath 'io.spring.gradle:spring-build-conventions:0.0.23.1.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" From 542c625d7d0074474f2f5faeb6dd6d0eee4768e3 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Tue, 2 Feb 2021 04:35:39 -0500 Subject: [PATCH 220/348] Allow null or empty authorities for DefaultOAuth2User Make DefaultOAuth2User more inline with other part of spring-security. For example, - DefaultOAuth2AuthenticatedPrincipal - AbstractAuthenticationToken Closes gh-9366 --- .../oauth2/core/user/DefaultOAuth2User.java | 12 +++++--- .../core/oidc/user/DefaultOidcUserTests.java | 28 +++++++++++++++---- .../core/user/DefaultOAuth2UserTests.java | 28 +++++++++++-------- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/DefaultOAuth2User.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/DefaultOAuth2User.java index 7657d6b1b7..b1231d2fa5 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/DefaultOAuth2User.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/DefaultOAuth2User.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -30,6 +30,8 @@ import java.util.SortedSet; import java.util.Comparator; import java.util.LinkedHashSet; +import org.springframework.security.core.authority.AuthorityUtils; + /** * The default implementation of an {@link OAuth2User}. * @@ -59,14 +61,16 @@ public class DefaultOAuth2User implements OAuth2User, Serializable { * @param attributes the attributes about the user * @param nameAttributeKey the key used to access the user's "name" from {@link #getAttributes()} */ - public DefaultOAuth2User(Collection authorities, Map attributes, String nameAttributeKey) { - Assert.notEmpty(authorities, "authorities cannot be empty"); + public DefaultOAuth2User(Collection authorities, Map attributes, + String nameAttributeKey) { Assert.notEmpty(attributes, "attributes cannot be empty"); Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty"); if (!attributes.containsKey(nameAttributeKey)) { throw new IllegalArgumentException("Missing attribute '" + nameAttributeKey + "' in attributes"); } - this.authorities = Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities))); + this.authorities = (authorities != null) + ? Collections.unmodifiableSet(new LinkedHashSet<>(this.sortAuthorities(authorities))) + : Collections.unmodifiableSet(new LinkedHashSet<>(AuthorityUtils.NO_AUTHORITIES)); this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); this.nameAttributeKey = nameAttributeKey; } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java index 2fad69684f..25ced4f4cb 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/oidc/user/DefaultOidcUserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 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. @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.core.oidc.user; import org.junit.Test; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcIdToken; @@ -57,11 +58,6 @@ public class DefaultOidcUserTests { private static final OidcIdToken ID_TOKEN = new OidcIdToken("id-token-value", Instant.EPOCH, Instant.MAX, ID_TOKEN_CLAIMS); private static final OidcUserInfo USER_INFO = new OidcUserInfo(USER_INFO_CLAIMS); - @Test(expected = IllegalArgumentException.class) - public void constructorWhenAuthoritiesIsNullThenThrowIllegalArgumentException() { - new DefaultOidcUser(null, ID_TOKEN); - } - @Test(expected = IllegalArgumentException.class) public void constructorWhenIdTokenIsNullThenThrowIllegalArgumentException() { new DefaultOidcUser(AUTHORITIES, null); @@ -72,6 +68,26 @@ public class DefaultOidcUserTests { new DefaultOidcUser(AUTHORITIES, ID_TOKEN, "invalid"); } + @Test + public void constructorWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() { + DefaultOidcUser user = new DefaultOidcUser(null, ID_TOKEN); + assertThat(user.getClaims()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB); + assertThat(user.getIdToken()).isEqualTo(ID_TOKEN); + assertThat(user.getName()).isEqualTo(SUBJECT); + assertThat(user.getAuthorities()).isEmpty(); + assertThat(user.getAttributes()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB); + } + + @Test + public void constructorWhenAuthoritiesIsEmptyThenCreated() { + DefaultOidcUser user = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, ID_TOKEN); + assertThat(user.getClaims()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB); + assertThat(user.getIdToken()).isEqualTo(ID_TOKEN); + assertThat(user.getName()).isEqualTo(SUBJECT); + assertThat(user.getAuthorities()).isEmpty(); + assertThat(user.getAttributes()).containsOnlyKeys(IdTokenClaimNames.ISS, IdTokenClaimNames.SUB); + } + @Test public void constructorWhenAuthoritiesIdTokenProvidedThenCreated() { DefaultOidcUser user = new DefaultOidcUser(AUTHORITIES, ID_TOKEN); diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/DefaultOAuth2UserTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/DefaultOAuth2UserTests.java index 3642b54abb..66c635f7c6 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/DefaultOAuth2UserTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/DefaultOAuth2UserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 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. @@ -41,16 +41,6 @@ public class DefaultOAuth2UserTests { private static final Map ATTRIBUTES = Collections.singletonMap( ATTRIBUTE_NAME_KEY, USERNAME); - @Test(expected = IllegalArgumentException.class) - public void constructorWhenAuthoritiesIsNullThenThrowIllegalArgumentException() { - new DefaultOAuth2User(null, ATTRIBUTES, ATTRIBUTE_NAME_KEY); - } - - @Test(expected = IllegalArgumentException.class) - public void constructorWhenAuthoritiesIsEmptyThenThrowIllegalArgumentException() { - new DefaultOAuth2User(Collections.emptySet(), ATTRIBUTES, ATTRIBUTE_NAME_KEY); - } - @Test(expected = IllegalArgumentException.class) public void constructorWhenAttributesIsNullThenThrowIllegalArgumentException() { new DefaultOAuth2User(AUTHORITIES, null, ATTRIBUTE_NAME_KEY); @@ -71,6 +61,22 @@ public class DefaultOAuth2UserTests { new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, "invalid"); } + @Test + public void constructorWhenAuthoritiesIsNullThenCreatedWithEmptyAuthorities() { + DefaultOAuth2User user = new DefaultOAuth2User(null, ATTRIBUTES, ATTRIBUTE_NAME_KEY); + assertThat(user.getName()).isEqualTo(USERNAME); + assertThat(user.getAuthorities()).isEmpty(); + assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY); + } + + @Test + public void constructorWhenAuthoritiesIsEmptyThenCreated() { + DefaultOAuth2User user = new DefaultOAuth2User(Collections.emptySet(), ATTRIBUTES, ATTRIBUTE_NAME_KEY); + assertThat(user.getName()).isEqualTo(USERNAME); + assertThat(user.getAuthorities()).isEmpty(); + assertThat(user.getAttributes()).containsOnlyKeys(ATTRIBUTE_NAME_KEY); + } + @Test public void constructorWhenAllParametersProvidedAndValidThenCreated() { DefaultOAuth2User user = new DefaultOAuth2User(AUTHORITIES, ATTRIBUTES, ATTRIBUTE_NAME_KEY); From 7a5c34ca576dba2889194bb8b2d719155698e7ab Mon Sep 17 00:00:00 2001 From: happier233 Date: Thu, 7 Jan 2021 00:06:05 +0800 Subject: [PATCH 221/348] Configure CurrentSecurityContextArgumentResolver BeanResolver Closes gh-9331 --- ...urrentSecurityContextArgumentResolver.java | 2 +- ...icationPrincipalArgumentResolverTests.java | 28 ++++++++++++++++++ ...tSecurityContextArgumentResolverTests.java | 29 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java b/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java index e05fc77bea..0ea3f858e8 100644 --- a/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java +++ b/web/src/main/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolver.java @@ -108,7 +108,7 @@ public final class CurrentSecurityContextArgumentResolver StandardEvaluationContext context = new StandardEvaluationContext(); context.setRootObject(securityContext); context.setVariable("this", securityContext); - + context.setBeanResolver(this.beanResolver); Expression expression = this.parser.parseExpression(expressionToParse); securityContextResult = expression.getValue(context); } diff --git a/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java index d7be109aec..36da3d5e61 100644 --- a/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java +++ b/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java @@ -22,11 +22,14 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; +import java.util.function.Function; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; +import org.springframework.expression.AccessException; +import org.springframework.expression.BeanResolver; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.AuthorityUtils; @@ -40,12 +43,21 @@ import org.springframework.util.ReflectionUtils; * */ public class AuthenticationPrincipalArgumentResolverTests { + + private final BeanResolver beanResolver = ((context, beanName) -> { + if (!"test".equals(beanName)) { + throw new AccessException("Could not resolve bean reference against BeanFactory"); + } + return (Function) (principal) -> principal.property; + }); + private Object expectedPrincipal; private AuthenticationPrincipalArgumentResolver resolver; @Before public void setup() { resolver = new AuthenticationPrincipalArgumentResolver(); + resolver.setBeanResolver(this.beanResolver); } @After @@ -128,6 +140,14 @@ public class AuthenticationPrincipalArgumentResolverTests { .isEqualTo(this.expectedPrincipal); } + @Test + public void resolveArgumentSpelBean() throws Exception { + CustomUserPrincipal principal = new CustomUserPrincipal(); + setAuthenticationPrincipal(principal); + this.expectedPrincipal = principal.property; + assertThat(this.resolver.resolveArgument(showUserSpelBean(), null, null, null)).isEqualTo(this.expectedPrincipal); + } + @Test public void resolveArgumentSpelCopy() throws Exception { CopyUserPrincipal principal = new CopyUserPrincipal("property"); @@ -198,6 +218,10 @@ public class AuthenticationPrincipalArgumentResolverTests { return getMethodParameter("showUserSpel", String.class); } + private MethodParameter showUserSpelBean() { + return getMethodParameter("showUserSpelBean", String.class); + } + private MethodParameter showUserSpelCopy() { return getMethodParameter("showUserSpelCopy", CopyUserPrincipal.class); } @@ -255,6 +279,10 @@ public class AuthenticationPrincipalArgumentResolverTests { @AuthenticationPrincipal(expression = "property") String user) { } + public void showUserSpelBean(@AuthenticationPrincipal( + expression = "@test.apply(#this)") String user) { + } + public void showUserSpelCopy( @AuthenticationPrincipal(expression = "new org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolverTests$CopyUserPrincipal(#this)") CopyUserPrincipal user) { } diff --git a/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java index 84dc2b2ce1..f7cc7b20d8 100644 --- a/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java +++ b/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java @@ -20,12 +20,15 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; +import java.util.function.Function; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; +import org.springframework.expression.AccessException; +import org.springframework.expression.BeanResolver; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; @@ -45,11 +48,20 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * */ public class CurrentSecurityContextArgumentResolverTests { + + private final BeanResolver beanResolver = ((context, beanName) -> { + if (!"test".equals(beanName)) { + throw new AccessException("Could not resolve bean reference against BeanFactory"); + } + return (Function) SecurityContext::getAuthentication; + }); + private CurrentSecurityContextArgumentResolver resolver; @Before public void setup() { this.resolver = new CurrentSecurityContextArgumentResolver(); + this.resolver.setBeanResolver(this.beanResolver); } @After @@ -104,6 +116,15 @@ public class CurrentSecurityContextArgumentResolverTests { assertThat(auth1.getPrincipal()).isEqualTo(principal); } + @Test + public void resolveArgumentWithAuthenticationWithBean() { + String principal = "john"; + setAuthenticationPrincipal(principal); + Authentication auth1 = (Authentication) this.resolver + .resolveArgument(showSecurityContextAuthenticationWithBean(), null, null, null); + assertThat(auth1.getPrincipal()).isEqualTo(principal); + } + @Test public void resolveArgumentWithNullAuthentication() { SecurityContext context = SecurityContextHolder.getContext(); @@ -217,6 +238,10 @@ public class CurrentSecurityContextArgumentResolverTests { return getMethodParameter("showSecurityContextAuthenticationAnnotation", Authentication.class); } + public MethodParameter showSecurityContextAuthenticationWithBean() { + return getMethodParameter("showSecurityContextAuthenticationWithBean", Authentication.class); + } + private MethodParameter showSecurityContextAuthenticationWithOptionalPrincipal() { return getMethodParameter("showSecurityContextAuthenticationWithOptionalPrincipal", Object.class); } @@ -279,6 +304,10 @@ public class CurrentSecurityContextArgumentResolverTests { public void showSecurityContextAuthenticationAnnotation(@CurrentSecurityContext(expression = "authentication") Authentication authentication) { } + public void showSecurityContextAuthenticationWithBean( + @CurrentSecurityContext(expression = "@test.apply(#this)") Authentication authentication) { + } + public void showSecurityContextAuthenticationWithOptionalPrincipal(@CurrentSecurityContext(expression = "authentication?.principal") Object principal) { } From 68ac3ef36b35cf2ef1e98c694fb9ac47381d4fce Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 3 Feb 2021 09:01:26 -0700 Subject: [PATCH 222/348] Polish Tests Issue gh-9331 --- ...icationPrincipalArgumentResolverTests.java | 24 ++++++++------- ...tSecurityContextArgumentResolverTests.java | 29 +++++++++---------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java index 36da3d5e61..d0251b990d 100644 --- a/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java +++ b/web/src/test/java/org/springframework/security/web/method/annotation/AuthenticationPrincipalArgumentResolverTests.java @@ -22,13 +22,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.util.function.Function; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; -import org.springframework.expression.AccessException; import org.springframework.expression.BeanResolver; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -38,24 +36,26 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.util.ReflectionUtils; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; + /** * @author Rob Winch * */ public class AuthenticationPrincipalArgumentResolverTests { - private final BeanResolver beanResolver = ((context, beanName) -> { - if (!"test".equals(beanName)) { - throw new AccessException("Could not resolve bean reference against BeanFactory"); - } - return (Function) (principal) -> principal.property; - }); + private BeanResolver beanResolver; private Object expectedPrincipal; private AuthenticationPrincipalArgumentResolver resolver; @Before public void setup() { + beanResolver = mock(BeanResolver.class); resolver = new AuthenticationPrincipalArgumentResolver(); resolver.setBeanResolver(this.beanResolver); } @@ -144,8 +144,11 @@ public class AuthenticationPrincipalArgumentResolverTests { public void resolveArgumentSpelBean() throws Exception { CustomUserPrincipal principal = new CustomUserPrincipal(); setAuthenticationPrincipal(principal); + when(this.beanResolver.resolve(any(), eq("test"))).thenReturn(principal.property); this.expectedPrincipal = principal.property; - assertThat(this.resolver.resolveArgument(showUserSpelBean(), null, null, null)).isEqualTo(this.expectedPrincipal); + assertThat(this.resolver.resolveArgument(showUserSpelBean(), null, null, null)) + .isEqualTo(this.expectedPrincipal); + verify(this.beanResolver).resolve(any(), eq("test")); } @Test @@ -279,8 +282,7 @@ public class AuthenticationPrincipalArgumentResolverTests { @AuthenticationPrincipal(expression = "property") String user) { } - public void showUserSpelBean(@AuthenticationPrincipal( - expression = "@test.apply(#this)") String user) { + public void showUserSpelBean(@AuthenticationPrincipal(expression = "@test") String user) { } public void showUserSpelCopy( diff --git a/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java b/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java index f7cc7b20d8..260b1b3967 100644 --- a/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java +++ b/web/src/test/java/org/springframework/security/web/method/annotation/CurrentSecurityContextArgumentResolverTests.java @@ -20,14 +20,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; -import java.util.function.Function; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.core.MethodParameter; -import org.springframework.expression.AccessException; import org.springframework.expression.BeanResolver; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.security.authentication.TestingAuthenticationToken; @@ -41,6 +39,11 @@ import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.mock; +import static org.mockito.BDDMockito.verify; +import static org.mockito.BDDMockito.when; /** * @author Dan Zheng @@ -49,17 +52,13 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; */ public class CurrentSecurityContextArgumentResolverTests { - private final BeanResolver beanResolver = ((context, beanName) -> { - if (!"test".equals(beanName)) { - throw new AccessException("Could not resolve bean reference against BeanFactory"); - } - return (Function) SecurityContext::getAuthentication; - }); + private BeanResolver beanResolver; private CurrentSecurityContextArgumentResolver resolver; @Before public void setup() { + this.beanResolver = mock(BeanResolver.class); this.resolver = new CurrentSecurityContextArgumentResolver(); this.resolver.setBeanResolver(this.beanResolver); } @@ -117,12 +116,12 @@ public class CurrentSecurityContextArgumentResolverTests { } @Test - public void resolveArgumentWithAuthenticationWithBean() { + public void resolveArgumentWithAuthenticationWithBean() throws Exception { String principal = "john"; - setAuthenticationPrincipal(principal); - Authentication auth1 = (Authentication) this.resolver - .resolveArgument(showSecurityContextAuthenticationWithBean(), null, null, null); - assertThat(auth1.getPrincipal()).isEqualTo(principal); + when(this.beanResolver.resolve(any(), eq("test"))).thenReturn(principal); + assertThat(this.resolver.resolveArgument(showSecurityContextAuthenticationWithBean(), null, null, null)) + .isEqualTo(principal); + verify(this.beanResolver).resolve(any(), eq("test")); } @Test @@ -239,7 +238,7 @@ public class CurrentSecurityContextArgumentResolverTests { } public MethodParameter showSecurityContextAuthenticationWithBean() { - return getMethodParameter("showSecurityContextAuthenticationWithBean", Authentication.class); + return getMethodParameter("showSecurityContextAuthenticationWithBean", String.class); } private MethodParameter showSecurityContextAuthenticationWithOptionalPrincipal() { @@ -305,7 +304,7 @@ public class CurrentSecurityContextArgumentResolverTests { } public void showSecurityContextAuthenticationWithBean( - @CurrentSecurityContext(expression = "@test.apply(#this)") Authentication authentication) { + @CurrentSecurityContext(expression = "@test") String name) { } public void showSecurityContextAuthenticationWithOptionalPrincipal(@CurrentSecurityContext(expression = "authentication?.principal") Object principal) { From 005eca7bd905bf1371e962cc9703fd2292fe1dc9 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Wed, 10 Feb 2021 11:31:30 -0700 Subject: [PATCH 223/348] Fix Test Configuration - Typo in PlaceholderConfig was causing Windows builds to resolve the CLASSPATH environment variable Closes gh-9421 --- .../config/web/server/OAuth2ResourceServerSpecTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java index 4bd3c17a0d..0c3203b13d 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java @@ -519,7 +519,7 @@ public class OAuth2ResourceServerSpecTests { @EnableWebFlux @EnableWebFluxSecurity static class PlaceholderConfig { - @Value("${classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub}") + @Value("classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub") RSAPublicKey key; @Bean From 1f19ee04e1da103a930406aec31614f244af8027 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:48:28 -0700 Subject: [PATCH 224/348] Update to Spring Boot 2.2.13.RELEASE Closes gh-9433 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 226f8a8ee1..246fa05f19 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.83 -springBootVersion=2.2.11.RELEASE +springBootVersion=2.2.13.RELEASE version=5.2.9.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 987b14d1d47577002a3c6424e3d76e9a127ba485 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:49:22 -0700 Subject: [PATCH 225/348] Update to Spring Framework 5.2.12.RELEASE Closes gh-9434 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 029ddaa274..cb39796e6b 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.11.RELEASE' + ext.springVersion = '5.2.12.RELEASE' } if (!project.hasProperty('springDataVersion')) { From 46bfc00db2a81c2b840c540d3809a8379c4fc4af Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:50:28 -0700 Subject: [PATCH 226/348] Update to Reactor Dysprosium-SR16 Closes gh-9435 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index cb39796e6b..6989fcbc31 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR14' + ext.reactorVersion = 'Dysprosium-SR16' } if (!project.hasProperty('springVersion')) { From 31cb29cb2d3f1f5a903fb0181d284bbc980bf7de Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:51:19 -0700 Subject: [PATCH 227/348] Update to Spring Data Moore-SR12 Closes gh-9436 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 6989fcbc31..e76f54836c 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR11' + ext.springDataVersion = 'Moore-SR12' } ext.rsocketVersion = '1.0.3' From 0fb60c3aa71800dfb8df6eb81c94ad1f21ed23fd Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:53:04 -0700 Subject: [PATCH 228/348] Update to thymeleaf-spring5 3.0.12 Closes gh-9437 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e76f54836c..8e21f0a3f9 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -211,7 +211,7 @@ dependencyManagement { dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' - dependency 'org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE' + dependency 'org.thymeleaf:thymeleaf-spring5:3.0.12.RELEASE' dependency 'org.unbescape:unbescape:1.1.5.RELEASE' dependency 'org.w3c.css:sac:1.3' dependency 'xalan:serializer:2.7.2' From db07cea579f650e4b2c37c394bdfe9e2070b6639 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:55:51 -0700 Subject: [PATCH 229/348] Update to hibernate-entitymanager 5.4.28.Final Closes gh-9438 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 8e21f0a3f9..1031848600 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.25.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.28.Final' dependency 'org.hibernate:hibernate-validator:6.1.6.Final' dependency 'org.hsqldb:hsqldb:2.5.1' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From 3cb6b3e5d69e9fd61bf82f9d11ca5b9a46aabde1 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:57:10 -0700 Subject: [PATCH 230/348] Update to hibernate-validator 6.1.7.Final Closes gh-9439 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 1031848600..f6fa251b63 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -183,7 +183,7 @@ dependencyManagement { dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' dependency 'org.hibernate:hibernate-entitymanager:5.4.28.Final' - dependency 'org.hibernate:hibernate-validator:6.1.6.Final' + dependency 'org.hibernate:hibernate-validator:6.1.7.Final' dependency 'org.hsqldb:hsqldb:2.5.1' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.javassist:javassist:3.22.0-CR2' From 44bb975f8209a99875f6b2352813f7e31c2f220f Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 08:59:02 -0700 Subject: [PATCH 231/348] Update to Jetty 9.4.36.v20210114 Closes gh-9440 --- gradle/dependency-management.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f6fa251b63..23b031b5fa 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -162,17 +162,17 @@ dependencyManagement { dependency 'org.codehaus.groovy:groovy-json:2.4.20' dependency 'org.codehaus.groovy:groovy:2.4.20' dependency 'org.eclipse.jdt:ecj:3.12.3' - dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.35.v20201120' - dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.35.v20201120' - dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-client:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-http:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-io:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-security:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-server:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-servlet:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-util:9.4.35.v20201120' - dependency 'org.eclipse.jetty:jetty-xml:9.4.35.v20201120' + dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.36.v20210114' + dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.36.v20210114' + dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-client:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-http:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-io:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-security:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-server:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-servlet:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-util:9.4.36.v20210114' + dependency 'org.eclipse.jetty:jetty-xml:9.4.36.v20210114' dependency 'org.eclipse.persistence:javax.persistence:2.2.1' dependency 'org.gebish:geb-ast:0.10.0' dependency 'org.gebish:geb-core:0.10.0' From f63b770ec51314736aff4f4d0e40d9b69bd18593 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 09:01:27 -0700 Subject: [PATCH 232/348] Update to Tomcat 9.0.43 Closes gh-9441 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 23b031b5fa..1a706a8f2e 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.38' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.38' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.38' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.38' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.43' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.43' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.43' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.43' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.38' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.43' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From f60daa51529c8330219528ec2ea99446da0bbc78 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 09:03:52 -0700 Subject: [PATCH 233/348] Update to GAE 1.9.86 Closes gh-9442 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 246fa05f19..15e2e8e23c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 -gaeVersion=1.9.83 +gaeVersion=1.9.86 springBootVersion=2.2.13.RELEASE version=5.2.9.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 7cab7b06c51ea885dd0d07ff26b135dd3afce1d1 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 28 Jan 2021 16:54:32 -0600 Subject: [PATCH 234/348] Optimize HttpSessionSecurityContextRepository Closes gh-9387 --- .../HttpSessionSecurityContextRepository.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java index 85f90e3f59..21719089fd 100644 --- a/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java +++ b/web/src/main/java/org/springframework/security/web/context/HttpSessionSecurityContextRepository.java @@ -142,13 +142,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo + response + ". You must use the HttpRequestResponseHolder.response after invoking loadContext"); } - // saveContext() might already be called by the response wrapper - // if something in the chain called sendError() or sendRedirect(). This ensures we - // only call it - // once per request. - if (!responseWrapper.isContextSaved()) { - responseWrapper.saveContext(context); - } + responseWrapper.saveContext(context); } public boolean containsContext(HttpServletRequest request) { @@ -305,6 +299,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo private final boolean httpSessionExistedAtStartOfRequest; private final SecurityContext contextBeforeExecution; private final Authentication authBeforeExecution; + private boolean isSaveContextInvoked; /** * Takes the parameters required to call saveContext() successfully @@ -355,6 +350,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo // SEC-1587 A non-anonymous context may still be in the session // SEC-1735 remove if the contextBeforeExecution was not anonymous httpSession.removeAttribute(springSecurityContextKey); + this.isSaveContextInvoked = true; } return; } @@ -371,7 +367,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) { httpSession.setAttribute(springSecurityContextKey, context); - + this.isSaveContextInvoked = true; if (logger.isDebugEnabled()) { logger.debug("SecurityContext '" + context + "' stored to HttpSession: '" + httpSession); @@ -381,7 +377,7 @@ public class HttpSessionSecurityContextRepository implements SecurityContextRepo } private boolean contextChanged(SecurityContext context) { - return context != contextBeforeExecution + return this.isSaveContextInvoked || context != contextBeforeExecution || context.getAuthentication() != authBeforeExecution; } From e2121532a2f86a379c3cd6f6e4bf7bd014311a9d Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 28 Jan 2021 17:01:21 -0600 Subject: [PATCH 235/348] Optimize HttpSessionSecurityContextRepository Closes gh-9387 --- ...SessionSecurityContextRepositoryTests.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java index 2975f61bb1..60b2a475df 100644 --- a/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java +++ b/web/src/test/java/org/springframework/security/web/context/HttpSessionSecurityContextRepositoryTests.java @@ -16,11 +16,15 @@ package org.springframework.security.web.context; +import java.io.IOException; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import javax.servlet.Filter; +import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; @@ -30,6 +34,7 @@ import javax.servlet.http.HttpSession; import org.junit.After; import org.junit.Test; +import org.springframework.mock.web.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -37,10 +42,14 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Transient; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -174,6 +183,48 @@ public class HttpSessionSecurityContextRepositoryTests { verify(session).setAttribute(SPRING_SECURITY_CONTEXT_KEY, ctx); } + + @Test + public void saveContextWhenSaveNewContextThenOriginalContextThenOriginalContextSaved() throws Exception { + HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository(); + SecurityContextPersistenceFilter securityContextPersistenceFilter = new SecurityContextPersistenceFilter( + repository); + + UserDetails original = User.withUsername("user").password("password").roles("USER").build(); + SecurityContext originalContext = createSecurityContext(original); + UserDetails impersonate = User.withUserDetails(original).username("impersonate").build(); + SecurityContext impersonateContext = createSecurityContext(impersonate); + + MockHttpServletRequest mockRequest = new MockHttpServletRequest(); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + + Filter saveImpersonateContext = (request, response, chain) -> { + SecurityContextHolder.setContext(impersonateContext); + // ensure the response is committed to trigger save + response.flushBuffer(); + chain.doFilter(request, response); + }; + Filter saveOriginalContext = (request, response, chain) -> { + SecurityContextHolder.setContext(originalContext); + chain.doFilter(request, response); + }; + HttpServlet servlet = new HttpServlet() { + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + resp.getWriter().write("Hi"); + } + }; + + SecurityContextHolder.setContext(originalContext); + MockFilterChain chain = new MockFilterChain(servlet, saveImpersonateContext, saveOriginalContext); + + securityContextPersistenceFilter.doFilter(mockRequest, mockResponse, chain); + + assertThat(mockRequest.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)) + .isEqualTo(originalContext); + } + @Test public void nonSecurityContextInSessionIsIgnored() { HttpSessionSecurityContextRepository repo = new HttpSessionSecurityContextRepository(); @@ -668,6 +719,13 @@ public class HttpSessionSecurityContextRepositoryTests { assertThat(session).isNull(); } + private SecurityContext createSecurityContext(UserDetails userDetails) { + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, + userDetails.getPassword(), userDetails.getAuthorities()); + SecurityContext securityContext = new SecurityContextImpl(token); + return securityContext; + } + @Transient private static class SomeTransientAuthentication extends AbstractAuthenticationToken { SomeTransientAuthentication() { From 974156d5fb2e05877ca3645b2afa483f1483c9d3 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 10:37:22 -0700 Subject: [PATCH 236/348] Release 5.2.9.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 15e2e8e23c..f7c8d07717 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.86 springBootVersion=2.2.13.RELEASE -version=5.2.9.BUILD-SNAPSHOT +version=5.2.9.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From bd0247adef8890cea7c00525c9803c44ccdc937a Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Thu, 11 Feb 2021 12:22:42 -0700 Subject: [PATCH 237/348] Next Development Version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f7c8d07717..9174a7c4ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.86 springBootVersion=2.2.13.RELEASE -version=5.2.9.RELEASE +version=5.2.10.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 8dc702c80f46d678ace50ca464c5ce599ae765bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BD=9A=E5=90=8D?= <1694392889@qq.com> Date: Tue, 6 Apr 2021 19:19:39 +0800 Subject: [PATCH 238/348] Add null check in CsrfFilter and CsrfWebFilter Solve the problem that CsrfFilter and CsrfWebFilter throws NPE exception when comparing two byte array is equal in low JDK version. When JDK version is lower than 1.8.0_45, method java.security.MessageDigest#isEqual does not verify whether the two arrays are null. And the above two class call this method without null judgment. ZiQiang Zhao<1694392889@qq.com> Closes gh-9561 --- .../security/web/csrf/CsrfFilter.java | 19 ++++++++++--------- .../web/server/csrf/CsrfWebFilter.java | 19 ++++++++++--------- .../security/web/csrf/CsrfFilterTests.java | 15 ++++++++++++++- .../web/server/csrf/CsrfWebFilterTests.java | 16 +++++++++++++++- 4 files changed, 49 insertions(+), 20 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java index 362168f109..8490c508da 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java +++ b/web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2021 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. @@ -174,17 +174,18 @@ public final class CsrfFilter extends OncePerRequestFilter { * @return */ private static boolean equalsConstantTime(String expected, String actual) { - byte[] expectedBytes = bytesUtf8(expected); - byte[] actualBytes = bytesUtf8(actual); + if (expected == actual) { + return true; + } + if (expected == null || actual == null) { + return false; + } + // Encode after ensure that the string is not null + byte[] expectedBytes = Utf8.encode(expected); + byte[] actualBytes = Utf8.encode(actual); return MessageDigest.isEqual(expectedBytes, actualBytes); } - private static byte[] bytesUtf8(String s) { - // need to check if Utf8.encode() runs in constant time (probably not). - // This may leak length of string. - return (s != null) ? Utf8.encode(s) : null; - } - private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { private final HashSet allowedMethods = new HashSet<>(Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS")); diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java index a2699018b3..e856905cf0 100644 --- a/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java +++ b/web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -177,17 +177,18 @@ public class CsrfWebFilter implements WebFilter { * @return */ private static boolean equalsConstantTime(String expected, String actual) { - byte[] expectedBytes = bytesUtf8(expected); - byte[] actualBytes = bytesUtf8(actual); + if (expected == actual) { + return true; + } + if (expected == null || actual == null) { + return false; + } + // Encode after ensure that the string is not null + byte[] expectedBytes = Utf8.encode(expected); + byte[] actualBytes = Utf8.encode(actual); return MessageDigest.isEqual(expectedBytes, actualBytes); } - private static byte[] bytesUtf8(String s) { - // need to check if Utf8.encode() runs in constant time (probably not). - // This may leak length of string. - return (s != null) ? Utf8.encode(s) : null; - } - private Mono generateToken(ServerWebExchange exchange) { return this.csrfTokenRepository.generateToken(exchange) .delayUntil((token) -> this.csrfTokenRepository.saveToken(exchange, token)); diff --git a/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java b/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java index 4129201cb4..68247b0888 100644 --- a/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/csrf/CsrfFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,7 @@ package org.springframework.security.web.csrf; import java.io.IOException; +import java.lang.reflect.Method; import java.util.Arrays; import javax.servlet.FilterChain; @@ -89,6 +90,18 @@ public class CsrfFilterTests { this.response = new MockHttpServletResponse(); } + @Test + public void nullConstantTimeEquals() throws Exception { + Method method = CsrfFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class); + method.setAccessible(true); + assertThat(method.invoke(CsrfFilter.class, null, null)).isEqualTo(true); + String expectedToken = "Hello—World"; + String actualToken = new String("Hello—World"); + assertThat(method.invoke(CsrfFilter.class, expectedToken, null)).isEqualTo(false); + assertThat(method.invoke(CsrfFilter.class, expectedToken, "hello-world")).isEqualTo(false); + assertThat(method.invoke(CsrfFilter.class, expectedToken, actualToken)).isEqualTo(true); + } + @Test(expected = IllegalArgumentException.class) public void constructorNullRepository() { new CsrfFilter(null); diff --git a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java index 617d61e1f6..208f44f4ec 100644 --- a/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -16,6 +16,8 @@ package org.springframework.security.web.server.csrf; +import java.lang.reflect.Method; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -67,6 +69,18 @@ public class CsrfWebFilterTests { private MockServerWebExchange post = MockServerWebExchange.from(MockServerHttpRequest.post("/")); + @Test + public void nullConstantTimeEquals() throws Exception { + Method method = CsrfWebFilter.class.getDeclaredMethod("equalsConstantTime", String.class, String.class); + method.setAccessible(true); + assertThat(method.invoke(CsrfWebFilter.class, null, null)).isEqualTo(true); + String expectedToken = "Hello—World"; + String actualToken = new String("Hello—World"); + assertThat(method.invoke(CsrfWebFilter.class, expectedToken, null)).isEqualTo(false); + assertThat(method.invoke(CsrfWebFilter.class, expectedToken, "hello-world")).isEqualTo(false); + assertThat(method.invoke(CsrfWebFilter.class, expectedToken, actualToken)).isEqualTo(true); + } + @Test public void filterWhenGetThenSessionNotCreatedAndChainContinues() { PublisherProbe chainResult = PublisherProbe.empty(); From 289b11b873a5b4974868d14aec1999e4f7b24f3e Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:50:22 +0200 Subject: [PATCH 239/348] Update to nohttp 0.0.6.RELEASE Closes gh-9609 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bd7f59749b..18cf56e8a0 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { dependencies { classpath 'io.spring.gradle:spring-build-conventions:0.0.23.1.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" - classpath 'io.spring.nohttp:nohttp-gradle:0.0.5.RELEASE' + classpath 'io.spring.nohttp:nohttp-gradle:0.0.6.RELEASE' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" } repositories { From cfc5256fad8d71e77ff04071aa3869afa1ae6be4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:50:46 +0200 Subject: [PATCH 240/348] Update to GAE 1.9.88 Closes gh-9608 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9174a7c4ad..f3cdbe802e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 -gaeVersion=1.9.86 +gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE version=5.2.10.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 78a618c260140aa74ee050f6c174ca6d1c9961ec Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:51:11 +0200 Subject: [PATCH 241/348] Update to Reactor Dysprosium-SR18 Closes gh-9599 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 1a706a8f2e..d9a4d3f66e 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR16' + ext.reactorVersion = 'Dysprosium-SR18' } if (!project.hasProperty('springVersion')) { From 6db79b70e62fd3313cd04f8a126ea5ac06d65a5f Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:51:41 +0200 Subject: [PATCH 242/348] Update to Spring Framework 5.2.13.RELEASE Close gh-9600 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d9a4d3f66e..ec9d54df2c 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.12.RELEASE' + ext.springVersion = '5.2.13.RELEASE' } if (!project.hasProperty('springDataVersion')) { From fb7efffad34818c2672f66bd1eedfff530992ce3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:52:09 +0200 Subject: [PATCH 243/348] Update to Spring Data Moore-SR13 Closes gh-9601 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ec9d54df2c..fa8eb73301 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -7,7 +7,7 @@ if (!project.hasProperty('springVersion')) { } if (!project.hasProperty('springDataVersion')) { - ext.springDataVersion = 'Moore-SR12' + ext.springDataVersion = 'Moore-SR13' } ext.rsocketVersion = '1.0.3' From 93defb2ff20f41b5c4aefb3c13a455ad7c8dd2a2 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:52:33 +0200 Subject: [PATCH 244/348] Update to RSocket 1.0.4 Closes gh-9602 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index fa8eb73301..a643361a6d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR13' } -ext.rsocketVersion = '1.0.3' +ext.rsocketVersion = '1.0.4' dependencyManagement { imports { From 310c1148ce8ea0f0876926818973b4bdd0b42d80 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:53:11 +0200 Subject: [PATCH 245/348] Update blockhound to 1.0.6.RELEASE Closes gh-9603 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index a643361a6d..c25c6cb96a 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -72,7 +72,7 @@ dependencyManagement { dependency 'commons-lang:commons-lang:2.6' dependency 'commons-logging:commons-logging:1.2' dependency 'dom4j:dom4j:1.6.1' - dependency 'io.projectreactor.tools:blockhound:1.0.4.RELEASE' + dependency 'io.projectreactor.tools:blockhound:1.0.6.RELEASE' dependency "io.rsocket:rsocket-core:${rsocketVersion}" dependency "io.rsocket:rsocket-transport-netty:${rsocketVersion}" dependency 'javax.activation:activation:1.1.1' From 41b0e51dbb21e0c8b69a0c18b49c240b85820efb Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:53:38 +0200 Subject: [PATCH 246/348] Update to embedded Apache Tomcat 9.0.45 Closes gh-9604 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index c25c6cb96a..e488724cb4 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.43' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.43' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.43' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.43' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.45' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.45' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.45' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.45' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.43' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.45' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From 5d18dd6d7dcc54e550d8ae5424b63824a30d3159 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:54:17 +0200 Subject: [PATCH 247/348] Update to Groovy 2.4.21 Closes gh-9605 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e488724cb4..49667618ce 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -158,9 +158,9 @@ dependencyManagement { dependency 'org.attoparser:attoparser:2.0.4.RELEASE' dependency 'org.bouncycastle:bcpkix-jdk15on:1.64' dependency 'org.bouncycastle:bcprov-jdk15on:1.64' - dependency 'org.codehaus.groovy:groovy-all:2.4.20' - dependency 'org.codehaus.groovy:groovy-json:2.4.20' - dependency 'org.codehaus.groovy:groovy:2.4.20' + dependency 'org.codehaus.groovy:groovy-all:2.4.21' + dependency 'org.codehaus.groovy:groovy-json:2.4.21' + dependency 'org.codehaus.groovy:groovy:2.4.21' dependency 'org.eclipse.jdt:ecj:3.12.3' dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.36.v20210114' dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.36.v20210114' From 59171434d5c56c82d082aa7eebd4fbd25ff17d27 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:54:38 +0200 Subject: [PATCH 248/348] Update to hibernate-entitymanager 5.4.30.Final Closes gh-9606 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 49667618ce..d55d4247c5 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.28.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.30.Final' dependency 'org.hibernate:hibernate-validator:6.1.7.Final' dependency 'org.hsqldb:hsqldb:2.5.1' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From b500b3ea695e5a95d04e9aa9dfd4a0b35eedbf96 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 10:55:05 +0200 Subject: [PATCH 249/348] Update to OpenSAML 3.4.6 Closes gh-9607 --- gradle/dependency-management.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d55d4247c5..41d25b5ebd 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -193,9 +193,9 @@ dependencyManagement { dependency 'org.mockito:mockito-core:3.0.0' dependency 'org.objenesis:objenesis:2.6' dependency 'org.openid4java:openid4java-nodeps:0.9.6' - dependency 'org.opensaml:opensaml-core:3.4.5' - dependency 'org.opensaml:opensaml-saml-api:3.4.5' - dependency 'org.opensaml:opensaml-saml-impl:3.4.5' + dependency 'org.opensaml:opensaml-core:3.4.6' + dependency 'org.opensaml:opensaml-saml-api:3.4.6' + dependency 'org.opensaml:opensaml-saml-impl:3.4.6' dependency 'org.ow2.asm:asm:6.2.1' dependency 'org.reactivestreams:reactive-streams:1.0.3' dependency 'org.seleniumhq.selenium:htmlunit-driver:2.36.0' From 46fdb250dcf74072c5c4e1006ac8a151fad586da Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 18:17:48 +0200 Subject: [PATCH 250/348] Release 5.2.10.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f3cdbe802e..7628e55169 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.10.BUILD-SNAPSHOT +version=5.2.10.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From ea19b311338249152a0ad92b28a6b0df402290b8 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 12 Apr 2021 19:00:02 +0200 Subject: [PATCH 251/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7628e55169..4ae3d6b667 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.10.RELEASE +version=5.2.11.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 521706d49650abf1059bcab36fe102906040b432 Mon Sep 17 00:00:00 2001 From: Denis Washington Date: Tue, 23 Feb 2021 22:57:43 +0100 Subject: [PATCH 252/348] Limit oauth2Login() links to redirect-based flows This prevents the generated login page from showing links for authorization grant types like "client_credentials" which are not redirect-based, and thus not meant for interactive use in the browser. Closes gh-9457 --- .../oauth2/client/OAuth2LoginConfigurer.java | 13 ++++--- .../config/web/server/ServerHttpSecurity.java | 9 +++-- .../client/OAuth2LoginConfigurerTests.java | 36 ++++++++++++++++++- .../config/web/server/OAuth2LoginTests.java | 33 ++++++++++++++++- 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java index d0ee3c6694..0d59af0167 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -57,6 +57,7 @@ import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequest import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; @@ -686,10 +687,12 @@ public final class OAuth2LoginConfigurer> exten this.authorizationEndpointConfig.authorizationRequestBaseUri : OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI; Map loginUrlToClientName = new HashMap<>(); - clientRegistrations.forEach(registration -> loginUrlToClientName.put( - authorizationRequestBaseUri + "/" + registration.getRegistrationId(), - registration.getClientName())); - + clientRegistrations.forEach((registration) -> { + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(registration.getAuthorizationGrantType())) { + String authorizationRequestUri = authorizationRequestBaseUri + "/" + registration.getRegistrationId(); + loginUrlToClientName.put(authorizationRequestUri, registration.getClientName()); + } + }); return loginUrlToClientName; } diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index df7d68597a..3780bba9be 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -85,6 +85,7 @@ import org.springframework.security.oauth2.client.web.server.ServerOAuth2Authori import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter; +import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.user.OAuth2User; @@ -1285,7 +1286,11 @@ public class ServerHttpSecurity { return Collections.emptyMap(); } Map result = new HashMap<>(); - registrations.iterator().forEachRemaining(r -> result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName())); + registrations.iterator().forEachRemaining((r) -> { + if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(r.getAuthorizationGrantType())) { + result.put("/oauth2/authorization/" + r.getRegistrationId(), r.getClientName()); + } + }); return result; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java index 8b35e92581..8af9d8b41b 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -116,6 +116,11 @@ public class OAuth2LoginConfigurerTests { .getBuilder("github").clientId("clientId").clientSecret("clientSecret") .build(); + // @formatter:off + private static final ClientRegistration CLIENT_CREDENTIALS_REGISTRATION = TestClientRegistrations.clientCredentials() + .build(); + // @formatter:on + private ConfigurableApplicationContext context; @Autowired @@ -431,6 +436,18 @@ public class OAuth2LoginConfigurerTests { assertThat(this.response.getRedirectedUrl()).doesNotMatch("http://localhost/oauth2/authorization/google"); } + // gh-9457 + @Test + public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredThenRedirectForAuthorization() + throws Exception { + loadConfig(OAuth2LoginConfigAuthorizationCodeClientAndOtherClients.class); + String requestUri = "/"; + this.request = new MockHttpServletRequest("GET", requestUri); + this.request.setServletPath(requestUri); + this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); + assertThat(this.response.getRedirectedUrl()).matches("http://localhost/oauth2/authorization/google"); + } + @Test public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws Exception { loadConfig(OAuth2LoginConfigCustomLoginPage.class); @@ -801,6 +818,23 @@ public class OAuth2LoginConfigurerTests { } } + @EnableWebSecurity + static class OAuth2LoginConfigAuthorizationCodeClientAndOtherClients extends CommonWebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .oauth2Login() + .clientRegistrationRepository( + new InMemoryClientRegistrationRepository( + GOOGLE_CLIENT_REGISTRATION, CLIENT_CREDENTIALS_REGISTRATION)); + // @formatter:on + super.configure(http); + } + + } + @EnableWebSecurity static class OAuth2LoginConfigCustomLoginPage extends CommonWebSecurityConfigurerAdapter { @Override diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index b1e5662a3e..5685de07b3 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -136,6 +136,11 @@ public class OAuth2LoginTests { .clientSecret("secret") .build(); + // @formatter:off + private static ClientRegistration clientCredentials = TestClientRegistrations.clientCredentials() + .build(); + // @formatter:on + @Autowired public void setApplicationContext(ApplicationContext context) { if (context.getBeanNamesForType(WebHandler.class).length > 0) { @@ -214,6 +219,32 @@ public class OAuth2LoginTests { } } + // gh-9457 + @Test + public void defaultLoginPageWithAuthorizationCodeAndClientCredentialsClientRegistrationThenRedirect() { + this.spring.register(OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration.class).autowire(); + // @formatter:off + WebTestClient webTestClient = WebTestClientBuilder + .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) + .build(); + WebDriver driver = WebTestClientHtmlUnitDriverBuilder + .webTestClientSetup(webTestClient) + .build(); + // @formatter:on + driver.get("http://localhost/"); + assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize"); + } + + @EnableWebFluxSecurity + static class OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration { + + @Bean + InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { + return new InMemoryReactiveClientRegistrationRepository(github, clientCredentials); + } + + } + @Test public void oauth2AuthorizeWhenCustomObjectsThenUsed() { this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, From adf3e94c9f2b15de45e5ce1e254f2ffd4f48bd70 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 14 Apr 2021 21:01:09 -0500 Subject: [PATCH 253/348] Fix HttpSecurity.addFilter* Ordering Closes gh-9633 --- ...ator.java => FilterOrderRegistration.java} | 78 +---------- .../annotation/web/builders/HttpSecurity.java | 81 ++++++++--- .../builders/HttpSecurityAddFilterTest.java | 132 ++++++++++++++++++ 3 files changed, 199 insertions(+), 92 deletions(-) rename config/src/main/java/org/springframework/security/config/annotation/web/builders/{FilterComparator.java => FilterOrderRegistration.java} (73%) create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java similarity index 73% rename from config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java rename to config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java index ab1fd36791..e37c14ae66 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterComparator.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java @@ -15,7 +15,6 @@ */ package org.springframework.security.config.annotation.web.builders; -import java.io.Serializable; import java.util.Comparator; import java.util.HashMap; import java.util.Map; @@ -53,14 +52,12 @@ import org.springframework.web.filter.CorsFilter; * @author Rob Winch * @since 3.2 */ - -@SuppressWarnings("serial") -final class FilterComparator implements Comparator, Serializable { +final class FilterOrderRegistration { private static final int INITIAL_ORDER = 100; private static final int ORDER_STEP = 100; private final Map filterToOrder = new HashMap<>(); - FilterComparator() { + FilterOrderRegistration() { Step order = new Step(INITIAL_ORDER, ORDER_STEP); put(ChannelProcessingFilter.class, order.next()); put(ConcurrentSessionFilter.class, order.next()); @@ -111,75 +108,6 @@ final class FilterComparator implements Comparator, Serializable { put(SwitchUserFilter.class, order.next()); } - public int compare(Filter lhs, Filter rhs) { - Integer left = getOrder(lhs.getClass()); - Integer right = getOrder(rhs.getClass()); - return left - right; - } - - /** - * Determines if a particular {@link Filter} is registered to be sorted - * - * @param filter - * @return - */ - public boolean isRegistered(Class filter) { - return getOrder(filter) != null; - } - - /** - * Registers a {@link Filter} to exist after a particular {@link Filter} that is - * already registered. - * @param filter the {@link Filter} to register - * @param afterFilter the {@link Filter} that is already registered and that - * {@code filter} should be placed after. - */ - public void registerAfter(Class filter, - Class afterFilter) { - Integer position = getOrder(afterFilter); - if (position == null) { - throw new IllegalArgumentException( - "Cannot register after unregistered Filter " + afterFilter); - } - - put(filter, position + 1); - } - - /** - * Registers a {@link Filter} to exist at a particular {@link Filter} position - * @param filter the {@link Filter} to register - * @param atFilter the {@link Filter} that is already registered and that - * {@code filter} should be placed at. - */ - public void registerAt(Class filter, - Class atFilter) { - Integer position = getOrder(atFilter); - if (position == null) { - throw new IllegalArgumentException( - "Cannot register after unregistered Filter " + atFilter); - } - - put(filter, position); - } - - /** - * Registers a {@link Filter} to exist before a particular {@link Filter} that is - * already registered. - * @param filter the {@link Filter} to register - * @param beforeFilter the {@link Filter} that is already registered and that - * {@code filter} should be placed before. - */ - public void registerBefore(Class filter, - Class beforeFilter) { - Integer position = getOrder(beforeFilter); - if (position == null) { - throw new IllegalArgumentException( - "Cannot register after unregistered Filter " + beforeFilter); - } - - put(filter, position - 1); - } - private void put(Class filter, int position) { String className = filter.getName(); filterToOrder.put(className, position); @@ -192,7 +120,7 @@ final class FilterComparator implements Comparator, Serializable { * @param clazz the {@link Filter} class to determine the sort order * @return the sort order or null if not defined */ - private Integer getOrder(Class clazz) { + Integer getOrder(Class clazz) { while (clazz != null) { Integer result = filterToOrder.get(clazz.getName()); if (result != null) { diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index bce40f4cd7..31a0e8686a 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -16,6 +16,8 @@ package org.springframework.security.config.annotation.web.builders; import org.springframework.context.ApplicationContext; +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; @@ -78,10 +80,16 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; + import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; /** @@ -125,9 +133,9 @@ public final class HttpSecurity extends implements SecurityBuilder, HttpSecurityBuilder { private final RequestMatcherConfigurer requestMatcherConfigurer; - private List filters = new ArrayList<>(); + private List filters = new ArrayList<>(); private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE; - private FilterComparator comparator = new FilterComparator(); + private FilterOrderRegistration filterOrders = new FilterOrderRegistration(); /** * Creates a new instance @@ -2528,8 +2536,12 @@ public final class HttpSecurity extends @Override protected DefaultSecurityFilterChain performBuild() { - filters.sort(comparator); - return new DefaultSecurityFilterChain(requestMatcher, filters); + this.filters.sort(OrderComparator.INSTANCE); + List sortedFilters = new ArrayList<>(this.filters.size()); + for (Filter filter : this.filters) { + sortedFilters.add(((OrderedFilter) filter).filter); + } + return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters); } /* @@ -2570,8 +2582,7 @@ public final class HttpSecurity extends * .servlet.Filter, java.lang.Class) */ public HttpSecurity addFilterAfter(Filter filter, Class afterFilter) { - comparator.registerAfter(filter.getClass(), afterFilter); - return addFilter(filter); + return addFilterAtOffsetOf(filter, 1, afterFilter); } /* @@ -2583,8 +2594,13 @@ public final class HttpSecurity extends */ public HttpSecurity addFilterBefore(Filter filter, Class beforeFilter) { - comparator.registerBefore(filter.getClass(), beforeFilter); - return addFilter(filter); + return addFilterAtOffsetOf(filter, -1, beforeFilter); + } + + private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class registeredFilter) { + int order = this.filterOrders.getOrder(registeredFilter) + offset; + this.filters.add(new OrderedFilter(filter, order)); + return this; } /* @@ -2595,14 +2611,12 @@ public final class HttpSecurity extends * servlet.Filter) */ public HttpSecurity addFilter(Filter filter) { - Class filterClass = filter.getClass(); - if (!comparator.isRegistered(filterClass)) { - throw new IllegalArgumentException( - "The Filter class " - + filterClass.getName() - + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); + Integer order = this.filterOrders.getOrder(filter.getClass()); + if (order == null) { + throw new IllegalArgumentException("The Filter class " + filter.getClass().getName() + + " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead."); } - this.filters.add(filter); + this.filters.add(new OrderedFilter(filter, order)); return this; } @@ -2626,8 +2640,7 @@ public final class HttpSecurity extends * @return the {@link HttpSecurity} for further customizations */ public HttpSecurity addFilterAt(Filter filter, Class atFilter) { - this.comparator.registerAt(filter.getClass(), atFilter); - return addFilter(filter); + return addFilterAtOffsetOf(filter, 0, atFilter); } /** @@ -3023,4 +3036,38 @@ public final class HttpSecurity extends } return apply(configurer); } + + /* + * A Filter that implements Ordered to be sorted. After sorting occurs, the original + * filter is what is used by FilterChainProxy + */ + private static final class OrderedFilter implements Ordered, Filter { + + private final Filter filter; + + private final int order; + + private OrderedFilter(Filter filter, int order) { + this.filter = filter; + this.order = order; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + this.filter.doFilter(servletRequest, servletResponse, filterChain); + } + + @Override + public int getOrder() { + return this.order; + } + + @Override + public String toString() { + return "OrderedFilter{" + "filter=" + this.filter + ", order=" + this.order + '}'; + } + + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java new file mode 100644 index 0000000000..b83818503f --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java @@ -0,0 +1,132 @@ +/* + * 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.config.annotation.web.builders; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.assertj.core.api.ListAssert; +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.access.channel.ChannelProcessingFilter; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class HttpSecurityAddFilterTest { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Test + public void addFilterAfterWhenSameFilterDifferentPlacesThenOrderCorrect() { + this.spring.register(MyFilterMultipleAfterConfig.class).autowire(); + + assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class, + ExceptionTranslationFilter.class, MyFilter.class); + } + + @Test + public void addFilterBeforeWhenSameFilterDifferentPlacesThenOrderCorrect() { + this.spring.register(MyFilterMultipleBeforeConfig.class).autowire(); + + assertThatFilters().containsSubsequence(MyFilter.class, WebAsyncManagerIntegrationFilter.class, MyFilter.class, + ExceptionTranslationFilter.class); + } + + @Test + public void addFilterAtWhenSameFilterDifferentPlacesThenOrderCorrect() { + this.spring.register(MyFilterMultipleAtConfig.class).autowire(); + + assertThatFilters().containsSubsequence(MyFilter.class, WebAsyncManagerIntegrationFilter.class, MyFilter.class, + ExceptionTranslationFilter.class); + } + + private ListAssert> assertThatFilters() { + FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); + List> filters = filterChain.getFilters("/").stream().map(Object::getClass) + .collect(Collectors.toList()); + return assertThat(filters); + } + + public static class MyFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse); + } + + } + + @EnableWebSecurity + static class MyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class) + .addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class); + // @formatter:on + } + + } + + @EnableWebSecurity + static class MyFilterMultipleBeforeConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class) + .addFilterBefore(new MyFilter(), ExceptionTranslationFilter.class); + // @formatter:on + } + + } + + @EnableWebSecurity + static class MyFilterMultipleAtConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterAt(new MyFilter(), ChannelProcessingFilter.class) + .addFilterAt(new MyFilter(), UsernamePasswordAuthenticationFilter.class); + // @formatter:on + } + + } + +} From ab34c0308cdc62a1f6b2f8037f572cb459147964 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 16 Apr 2021 11:59:01 -0400 Subject: [PATCH 254/348] Add guard around logger.debug statement The log message involves string concatenation, the cost of which should only be incurred if debug logging is enabled Issue gh-9648 --- .../web/session/SimpleRedirectInvalidSessionStrategy.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java b/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java index 050f282fbb..8f6eab2e00 100644 --- a/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java +++ b/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java @@ -46,8 +46,10 @@ public final class SimpleRedirectInvalidSessionStrategy implements InvalidSessio public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException { - logger.debug("Starting new session (if required) and redirecting to '" - + destinationUrl + "'"); + if (logger.isDebugEnabled()) { + logger.debug("Starting new session (if required) and redirecting to '" + + destinationUrl + "'"); + } if (createNewSession) { request.getSession(); } From 99db0ca2c5f39de515077c857ac0507c3dc23c46 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 14 Apr 2021 13:39:41 -0400 Subject: [PATCH 255/348] WebFlux httpBasic() matches on XHR requests Closes gh-9660 --- .../config/web/server/ServerHttpSecurity.java | 28 +++++++++++++++++-- .../web/server/ServerHttpSecurityTests.java | 24 +++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 3780bba9be..dfc8b3c24e 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -33,6 +33,7 @@ import java.util.function.Supplier; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository; +import org.springframework.http.HttpStatus; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; import reactor.core.publisher.Mono; @@ -113,6 +114,7 @@ import org.springframework.security.web.server.WebFilterExchange; import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; @@ -2965,11 +2967,17 @@ public class ServerHttpSecurity { * @see #httpBasic() */ public class HttpBasicSpec { + + private final ServerWebExchangeMatcher xhrMatcher = (exchange) -> Mono.just(exchange.getRequest().getHeaders()) + .filter((h) -> h.getOrEmpty("X-Requested-With").contains("XMLHttpRequest")) + .flatMap((h) -> ServerWebExchangeMatcher.MatchResult.match()) + .switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch()); + private ReactiveAuthenticationManager authenticationManager; private ServerSecurityContextRepository securityContextRepository; - private ServerAuthenticationEntryPoint entryPoint = new HttpBasicServerAuthenticationEntryPoint(); + private ServerAuthenticationEntryPoint entryPoint; /** * The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to @@ -3034,7 +3042,13 @@ public class ServerHttpSecurity { MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML); restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL)); - ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(restMatcher, this.entryPoint)); + ServerWebExchangeMatcher notHtmlMatcher = new NegatedServerWebExchangeMatcher( + new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML)); + ServerWebExchangeMatcher restNotHtmlMatcher = new AndServerWebExchangeMatcher( + Arrays.asList(notHtmlMatcher, restMatcher)); + ServerWebExchangeMatcher preferredMatcher = new OrServerWebExchangeMatcher( + Arrays.asList(this.xhrMatcher, restNotHtmlMatcher)); + ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint)); AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter( this.authenticationManager); authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint)); @@ -3043,7 +3057,15 @@ public class ServerHttpSecurity { http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC); } - private HttpBasicSpec() {} + private HttpBasicSpec() { + List entryPoints = new ArrayList<>(); + entryPoints + .add(new DelegateEntry(this.xhrMatcher, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))); + DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint( + entryPoints); + defaultEntryPoint.setDefaultEntryPoint(new HttpBasicServerAuthenticationEntryPoint()); + this.entryPoint = defaultEntryPoint; + } } /** diff --git a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java index 052b6629a4..cb7efee35b 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -41,6 +41,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; @@ -48,6 +49,8 @@ import org.springframework.security.oauth2.client.web.server.authentication.OAut import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; +import org.springframework.security.web.server.ServerAuthenticationEntryPoint; +import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter; import org.springframework.security.web.server.savedrequest.ServerRequestCache; import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache; @@ -184,6 +187,25 @@ public class ServerHttpSecurityTests { .expectBody().isEmpty(); } + @Test + public void basicWhenXHRRequestThenUnauthorized() { + ServerAuthenticationEntryPoint authenticationEntryPoint = spy( + new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)); + this.http.httpBasic().authenticationEntryPoint(authenticationEntryPoint); + this.http.authorizeExchange().anyExchange().authenticated(); + WebTestClient client = buildClient(); + // @formatter:off + client.get().uri("/") + .header("X-Requested-With", "XMLHttpRequest") + .exchange() + .expectStatus().isUnauthorized() + .expectHeader().doesNotExist("WWW-Authenticate") + .expectHeader().valueMatches(HttpHeaders.CACHE_CONTROL, ".+") + .expectBody().isEmpty(); + // @formatter:on + verify(authenticationEntryPoint).commence(any(), any()); + } + @Test public void buildWhenServerWebExchangeFromContextThenFound() { SecurityWebFilterChain filter = this.http.build(); From 0a56dc4ef50edc53899499064042ca9f4f7e3971 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 27 Apr 2021 09:54:05 -0500 Subject: [PATCH 256/348] docs.af.pivotal.io->docs-ip.spring.io Closes gh-9686 --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a7ee4c2be3..53349704dc 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -176,7 +176,7 @@ try { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PdeployDocsHost=docs-ip.spring.io --refresh-dependencies --no-daemon --stacktrace" } } } @@ -191,7 +191,7 @@ try { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PdeployDocsHost=docs-ip.spring.io --refresh-dependencies --no-daemon --stacktrace" } } } From 362855b8b83dc81058bd0d4db1c11635d580adcc Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 27 Apr 2021 10:25:48 -0500 Subject: [PATCH 257/348] docs.af.pivotal.io->docs-ip.spring.io The build conventions plugin does not support a property, so we must override the configuration for docs.host to docs-ip.spring.io Closes gh-9686 --- Jenkinsfile | 4 ++-- docs/manual/spring-security-docs-manual.gradle | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 53349704dc..a7ee4c2be3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -176,7 +176,7 @@ try { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PdeployDocsHost=docs-ip.spring.io --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } @@ -191,7 +191,7 @@ try { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PdeployDocsHost=docs-ip.spring.io --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" } } } diff --git a/docs/manual/spring-security-docs-manual.gradle b/docs/manual/spring-security-docs-manual.gradle index 8056b92bf0..0d59ce2dca 100644 --- a/docs/manual/spring-security-docs-manual.gradle +++ b/docs/manual/spring-security-docs-manual.gradle @@ -15,6 +15,7 @@ remotes { docs { retryCount = 5 // retry 5 times (default is 0) retryWaitSec = 10 // wait 10 seconds between retries (default is 0) + host = "docs-ip.spring.io" } } From ecb4a5749aeddf77bf37b4255279ba19af0381f6 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 22 Apr 2021 15:16:52 -0400 Subject: [PATCH 258/348] HttpSessionOAuth2AuthorizationRequestRepository: store one request by default Add setAllowMultipleAuthorizationRequests allowing applications to revert to the previous functionality should they need to do so. Closes gh-5145 Intentionally regresses gh-5110 --- ...nOAuth2AuthorizationRequestRepository.java | 62 ++++++++++++--- ...lowMultipleAuthorizationRequestsTests.java | 76 +++++++++++++++++++ ...lowMultipleAuthorizationRequestsTests.java | 76 +++++++++++++++++++ ...h2AuthorizationRequestRepositoryTests.java | 53 ++----------- 4 files changed, 211 insertions(+), 56 deletions(-) create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepository.java index 58ea54f53e..14d04bb9ba 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepository.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -31,6 +31,7 @@ import java.util.Map; * * @author Joe Grandja * @author Rob Winch + * @author Craig Andrews * @since 5.0 * @see AuthorizationRequestRepository * @see OAuth2AuthorizationRequest @@ -41,6 +42,8 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME; + private boolean allowMultipleAuthorizationRequests; + @Override public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { Assert.notNull(request, "request cannot be null"); @@ -63,9 +66,14 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au } String state = authorizationRequest.getState(); Assert.hasText(state, "authorizationRequest.state cannot be empty"); - Map authorizationRequests = this.getAuthorizationRequests(request); - authorizationRequests.put(state, authorizationRequest); - request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests); + if (this.allowMultipleAuthorizationRequests) { + Map authorizationRequests = this.getAuthorizationRequests(request); + authorizationRequests.put(state, authorizationRequest); + request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests); + } + else { + request.getSession().setAttribute(this.sessionAttributeName, authorizationRequest); + } } @Override @@ -77,11 +85,16 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au } Map authorizationRequests = this.getAuthorizationRequests(request); OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(stateParameter); - if (!authorizationRequests.isEmpty()) { - request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests); - } else { + if (authorizationRequests.size() == 0) { request.getSession().removeAttribute(this.sessionAttributeName); } + else if (authorizationRequests.size() == 1) { + request.getSession().setAttribute(this.sessionAttributeName, + authorizationRequests.values().iterator().next()); + } + else { + request.getSession().setAttribute(this.sessionAttributeName, authorizationRequests); + } return originalRequest; } @@ -107,11 +120,38 @@ public final class HttpSessionOAuth2AuthorizationRequestRepository implements Au */ private Map getAuthorizationRequests(HttpServletRequest request) { HttpSession session = request.getSession(false); - Map authorizationRequests = session == null ? null : - (Map) session.getAttribute(this.sessionAttributeName); - if (authorizationRequests == null) { + Object sessionAttributeValue = (session != null) ? session.getAttribute(this.sessionAttributeName) : null; + if (sessionAttributeValue == null) { return new HashMap<>(); } - return authorizationRequests; + else if (sessionAttributeValue instanceof OAuth2AuthorizationRequest) { + OAuth2AuthorizationRequest auth2AuthorizationRequest = (OAuth2AuthorizationRequest) sessionAttributeValue; + Map authorizationRequests = new HashMap<>(1); + authorizationRequests.put(auth2AuthorizationRequest.getState(), auth2AuthorizationRequest); + return authorizationRequests; + } + else if (sessionAttributeValue instanceof Map) { + @SuppressWarnings("unchecked") + Map authorizationRequests = (Map) sessionAttributeValue; + return authorizationRequests; + } + else { + throw new IllegalStateException( + "authorizationRequests is supposed to be a Map or OAuth2AuthorizationRequest but actually is a " + + sessionAttributeValue.getClass()); + } + } + + /** + * Configure if multiple {@link OAuth2AuthorizationRequest}s should be stored per + * session. Default is false (not allow multiple {@link OAuth2AuthorizationRequest} + * per session). + * @param allowMultipleAuthorizationRequests true allows more than one + * {@link OAuth2AuthorizationRequest} to be stored per session. + * @since 5.5 + */ + @Deprecated + public void setAllowMultipleAuthorizationRequests(boolean allowMultipleAuthorizationRequests) { + this.allowMultipleAuthorizationRequests = allowMultipleAuthorizationRequests; } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java new file mode 100644 index 0000000000..0d245fb04c --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2021 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.client.web; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository} when + * {@link HttpSessionOAuth2AuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)} + * is enabled. + * + * @author Joe Grandja + * @author Craig Andrews + */ +public class HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests + extends HttpSessionOAuth2AuthorizationRequestRepositoryTests { + + @Before + public void setup() { + this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + this.authorizationRequestRepository.setAllowMultipleAuthorizationRequests(true); + } + + // gh-5110 + @Test + public void loadAuthorizationRequestWhenMultipleSavedThenReturnMatchingAuthorizationRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build(); + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build(); + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response); + String state3 = "state-5566"; + OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build(); + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response); + request.addParameter(OAuth2ParameterNames.STATE, state1); + OAuth2AuthorizationRequest loadedAuthorizationRequest1 = this.authorizationRequestRepository + .loadAuthorizationRequest(request); + assertThat(loadedAuthorizationRequest1).isEqualTo(authorizationRequest1); + request.removeParameter(OAuth2ParameterNames.STATE); + request.addParameter(OAuth2ParameterNames.STATE, state2); + OAuth2AuthorizationRequest loadedAuthorizationRequest2 = this.authorizationRequestRepository + .loadAuthorizationRequest(request); + assertThat(loadedAuthorizationRequest2).isEqualTo(authorizationRequest2); + request.removeParameter(OAuth2ParameterNames.STATE); + request.addParameter(OAuth2ParameterNames.STATE, state3); + OAuth2AuthorizationRequest loadedAuthorizationRequest3 = this.authorizationRequestRepository + .loadAuthorizationRequest(request); + assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java new file mode 100644 index 0000000000..a9b1ebbe4e --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2021 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.client.web; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository} when + * {@link HttpSessionOAuth2AuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)} + * is disabled. + * + * @author Joe Grandja + * @author Craig Andrews + */ +public class HttpSessionOAuth2AuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests + extends HttpSessionOAuth2AuthorizationRequestRepositoryTests { + + @Before + public void setup() { + this.authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); + this.authorizationRequestRepository.setAllowMultipleAuthorizationRequests(false); + } + + // gh-5145 + @Test + public void loadAuthorizationRequestWhenMultipleSavedThenReturnLastAuthorizationRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build(); + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build(); + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response); + String state3 = "state-5566"; + OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build(); + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response); + request.addParameter(OAuth2ParameterNames.STATE, state1); + OAuth2AuthorizationRequest loadedAuthorizationRequest1 = this.authorizationRequestRepository + .loadAuthorizationRequest(request); + assertThat(loadedAuthorizationRequest1).isNull(); + request.removeParameter(OAuth2ParameterNames.STATE); + request.addParameter(OAuth2ParameterNames.STATE, state2); + OAuth2AuthorizationRequest loadedAuthorizationRequest2 = this.authorizationRequestRepository + .loadAuthorizationRequest(request); + assertThat(loadedAuthorizationRequest2).isNull(); + request.removeParameter(OAuth2ParameterNames.STATE); + request.addParameter(OAuth2ParameterNames.STATE, state3); + OAuth2AuthorizationRequest loadedAuthorizationRequest3 = this.authorizationRequestRepository + .loadAuthorizationRequest(request); + assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryTests.java index fe8fc5514a..e7b33bfe0b 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 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. @@ -34,11 +34,12 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; * Tests for {@link HttpSessionOAuth2AuthorizationRequestRepository}. * * @author Joe Grandja + * @author Craig Andrews */ @RunWith(MockitoJUnitRunner.class) -public class HttpSessionOAuth2AuthorizationRequestRepositoryTests { - private HttpSessionOAuth2AuthorizationRequestRepository authorizationRequestRepository = - new HttpSessionOAuth2AuthorizationRequestRepository(); +public abstract class HttpSessionOAuth2AuthorizationRequestRepositoryTests { + + protected HttpSessionOAuth2AuthorizationRequestRepository authorizationRequestRepository; @Test(expected = IllegalArgumentException.class) public void loadAuthorizationRequestWhenHttpServletRequestIsNullThenThrowIllegalArgumentException() { @@ -70,42 +71,6 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryTests { assertThat(loadedAuthorizationRequest).isEqualTo(authorizationRequest); } - // gh-5110 - @Test - public void loadAuthorizationRequestWhenMultipleSavedThenReturnMatchingAuthorizationRequest() { - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - - String state1 = "state-1122"; - OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest1, request, response); - - String state2 = "state-3344"; - OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest2, request, response); - - String state3 = "state-5566"; - OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response); - - request.addParameter(OAuth2ParameterNames.STATE, state1); - OAuth2AuthorizationRequest loadedAuthorizationRequest1 = - this.authorizationRequestRepository.loadAuthorizationRequest(request); - assertThat(loadedAuthorizationRequest1).isEqualTo(authorizationRequest1); - - request.removeParameter(OAuth2ParameterNames.STATE); - request.addParameter(OAuth2ParameterNames.STATE, state2); - OAuth2AuthorizationRequest loadedAuthorizationRequest2 = - this.authorizationRequestRepository.loadAuthorizationRequest(request); - assertThat(loadedAuthorizationRequest2).isEqualTo(authorizationRequest2); - - request.removeParameter(OAuth2ParameterNames.STATE); - request.addParameter(OAuth2ParameterNames.STATE, state3); - OAuth2AuthorizationRequest loadedAuthorizationRequest3 = - this.authorizationRequestRepository.loadAuthorizationRequest(request); - assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3); - } - @Test public void loadAuthorizationRequestWhenSavedAndStateParameterNullThenReturnNull() { MockHttpServletRequest request = new MockHttpServletRequest(); @@ -284,11 +249,9 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryTests { assertThat(removedAuthorizationRequest).isNull(); } - private OAuth2AuthorizationRequest.Builder createAuthorizationRequest() { - return OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://example.com/oauth2/authorize") - .clientId("client-id-1234") - .state("state-1234"); + protected OAuth2AuthorizationRequest.Builder createAuthorizationRequest() { + return OAuth2AuthorizationRequest.authorizationCode().authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id-1234").state("state-1234"); } static class MockDistributedHttpSession extends MockHttpSession { From c9a8419e22f3397bb588a5bf5186202da151cb91 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 12 May 2021 14:35:29 -0500 Subject: [PATCH 259/348] Additional HttpSessionOAuth2AuthorizationRequestRepository tests Issue gh-5145 --- ...lowMultipleAuthorizationRequestsTests.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java index 0d245fb04c..988829dc80 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java @@ -73,4 +73,49 @@ public class HttpSessionOAuth2AuthorizationRequestRepositoryAllowMultipleAuthori assertThat(loadedAuthorizationRequest3).isEqualTo(authorizationRequest3); } + @Test + public void loadAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequests() { + // save 2 requests with legacy (allowMultipleAuthorizationRequests=true) and load + // with new + HttpSessionOAuth2AuthorizationRequestRepository legacy = new HttpSessionOAuth2AuthorizationRequestRepository(); + legacy.setAllowMultipleAuthorizationRequests(true); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build(); + legacy.saveAuthorizationRequest(authorizationRequest1, request, response); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build(); + legacy.saveAuthorizationRequest(authorizationRequest2, request, response); + + request.setParameter(OAuth2ParameterNames.STATE, state1); + OAuth2AuthorizationRequest loaded = this.authorizationRequestRepository.loadAuthorizationRequest(request); + + assertThat(loaded).isEqualTo(authorizationRequest1); + } + + @Test + public void saveAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequests() { + // save 2 requests with legacy (allowMultipleAuthorizationRequests=true), save + // with new, and load with new + HttpSessionOAuth2AuthorizationRequestRepository legacy = new HttpSessionOAuth2AuthorizationRequestRepository(); + legacy.setAllowMultipleAuthorizationRequests(true); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = createAuthorizationRequest().state(state1).build(); + legacy.saveAuthorizationRequest(authorizationRequest1, request, response); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = createAuthorizationRequest().state(state2).build(); + legacy.saveAuthorizationRequest(authorizationRequest2, request, response); + String state3 = "state-5566"; + OAuth2AuthorizationRequest authorizationRequest3 = createAuthorizationRequest().state(state3).build(); + + this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest3, request, response); + request.setParameter(OAuth2ParameterNames.STATE, state3); + OAuth2AuthorizationRequest loaded = this.authorizationRequestRepository.loadAuthorizationRequest(request); + + assertThat(loaded).isEqualTo(authorizationRequest3); + } + } From d3a3c36ad3e9579127a40d057b99c6bd21f546ba Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Tue, 18 May 2021 14:00:56 -0500 Subject: [PATCH 260/348] Handle custom status codes in error handler Fixes an issue where custom status codes in the error response cause an IllegalArgumentException to be thrown when resolving an HttpStatus. Closes gh-9741 --- .../http/OAuth2ErrorResponseErrorHandler.java | 2 +- .../OAuth2ErrorResponseErrorHandlerTests.java | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java index 3f865a5897..fb74797e80 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandler.java @@ -48,7 +48,7 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler { @Override public void handleError(ClientHttpResponse response) throws IOException { - if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode())) { + if (HttpStatus.BAD_REQUEST.value() != response.getRawStatusCode()) { this.defaultErrorHandler.handleError(response); } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java index 98e2b72bb2..731d16cdd9 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/http/OAuth2ErrorResponseErrorHandlerTests.java @@ -15,12 +15,19 @@ */ package org.springframework.security.oauth2.client.http; +import java.io.IOException; + import org.junit.Test; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.MockHttpInputMessage; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; +import org.springframework.web.client.UnknownHttpStatusCodeException; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** @@ -58,4 +65,49 @@ public class OAuth2ErrorResponseErrorHandlerTests { .isInstanceOf(OAuth2AuthorizationException.class) .hasMessage("[insufficient_scope] The access token expired"); } + + @Test + public void handleErrorWhenErrorResponseWithInvalidStatusCodeThenHandled() { + CustomMockClientHttpResponse response = new CustomMockClientHttpResponse(new byte[0], 596); + assertThatExceptionOfType(UnknownHttpStatusCodeException.class) + .isThrownBy(() -> this.errorHandler.handleError(response)).withMessage("596 : [no body]"); + } + + private static final class CustomMockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse { + + private final int statusCode; + + private CustomMockClientHttpResponse(byte[] content, int statusCode) { + super(content); + this.statusCode = statusCode; + } + + @Override + public HttpStatus getStatusCode() throws IOException { + return HttpStatus.valueOf(getRawStatusCode()); + } + + @Override + public int getRawStatusCode() { + return this.statusCode; + } + + @Override + public String getStatusText() throws IOException { + HttpStatus httpStatus = HttpStatus.resolve(this.statusCode); + return (httpStatus != null) ? httpStatus.getReasonPhrase() : ""; + } + + @Override + public void close() { + try { + getBody().close(); + } + catch (IOException ex) { + // ignore + } + } + + } + } From c79cb8eff653f9a01de6b13a92c9ea8120bf6ed0 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Tue, 18 May 2021 11:00:15 -0500 Subject: [PATCH 261/348] Handle encoded spaces in the root dn Fixes an issue where provider URLs passed to the constructor of the DefaultSpringSecurityContextSource can be URL encoded, resulting in an invalid base dn. Additionally adds support for list constructor to support spaces in base dn. Closes gh-9742 # Conflicts: # ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java # ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java --- ...faultSpringSecurityContextSourceTests.java | 26 +++++- ...terBasedLdapUserSearchWithSpacesTests.java | 89 +++++++++++++++++++ .../resources/test-server-with-spaces.ldif | 14 +++ .../DefaultSpringSecurityContextSource.java | 30 ++++++- 4 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java create mode 100644 ldap/src/integration-test/resources/test-server-with-spaces.ldif diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java index e2da339fcf..f88554e16d 100644 --- a/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/DefaultSpringSecurityContextSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -53,8 +53,17 @@ public class DefaultSpringSecurityContextSourceTests { @Test public void supportsSpacesInUrl() { - new DefaultSpringSecurityContextSource( + DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource( "ldap://myhost:10389/dc=spring%20framework,dc=org"); + assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org"); + } + + // gh-9742 + @Test + public void constructorWhenUrlEncodedSpacesWithPlusCharacterThenBaseDnIsProperlyDecoded() { + DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource( + "ldap://blah:123/dc=spring+framework,dc=org ldap://blah:456/dc=spring+framework,dc=org"); + assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org"); } @Test @@ -105,6 +114,7 @@ public class DefaultSpringSecurityContextSourceTests { DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource( this.contextSource.getUrls()[0] + "ou=space%20cadets,dc=springframework,dc=org"); + assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("ou=space cadets,dc=springframework,dc=org"); contextSource.afterPropertiesSet(); contextSource.getContext( "uid=space cadet,ou=space cadets,dc=springframework,dc=org", @@ -147,6 +157,18 @@ public class DefaultSpringSecurityContextSourceTests { assertThat(ctxSrc.isPooled()).isTrue(); } + // gh-9742 + @Test + public void constructorWhenServerListWithSpacesInBaseDnThenSuccess() { + List serverUrls = new ArrayList<>(); + serverUrls.add("ldap://ad1.example.org:789"); + serverUrls.add("ldap://ad2.example.org:389"); + serverUrls.add("ldaps://ad3.example.org:636"); + DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(serverUrls, + "dc=spring framework,dc=org"); + assertThat(contextSource.getBaseLdapPathAsString()).isEqualTo("dc=spring framework,dc=org"); + } + @Test(expected = IllegalArgumentException.class) public void instantiationFailsWithIncorrectServerUrl() { List serverUrls = new ArrayList<>(); diff --git a/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java b/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java new file mode 100644 index 0000000000..3d2643f4fa --- /dev/null +++ b/ldap/src/integration-test/java/org/springframework/security/ldap/search/FilterBasedLdapUserSearchWithSpacesTests.java @@ -0,0 +1,89 @@ +/* + * Copyright 2002-2021 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.ldap.search; + +import javax.naming.ldap.LdapName; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DirContextOperations; +import org.springframework.security.ldap.DefaultSpringSecurityContextSource; +import org.springframework.security.ldap.server.ApacheDSContainer; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Additional tests for {@link FilterBasedLdapUserSearch} with spaces in the base dn. + * + * @author Steve Riesenberg + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = FilterBasedLdapUserSearchWithSpacesTests.ApacheDsContainerWithSpacesConfig.class) +public class FilterBasedLdapUserSearchWithSpacesTests { + + @Autowired + private DefaultSpringSecurityContextSource contextSource; + + // gh-9742 + @Test + public void searchForUserWhenSpacesInBaseDnThenSuccess() throws Exception { + FilterBasedLdapUserSearch locator = new FilterBasedLdapUserSearch("ou=space cadets", "(uid={0})", + this.contextSource); + locator.setSearchSubtree(false); + locator.setSearchTimeLimit(0); + locator.setDerefLinkFlag(false); + + DirContextOperations bob = locator.searchForUser("space cadet"); + assertThat(bob.getStringAttribute("uid")).isEqualTo("space cadet"); + assertThat(bob.getDn()).isEqualTo(new LdapName("uid=space cadet,ou=space cadets")); + } + + @Configuration + static class ApacheDsContainerWithSpacesConfig implements DisposableBean { + + private ApacheDSContainer container; + + @Bean + ApacheDSContainer ldapContainer() throws Exception { + this.container = new ApacheDSContainer("dc=spring framework,dc=org", + "classpath:test-server-with-spaces.ldif"); + this.container.setPort(53390); + return this.container; + } + + @Bean + ContextSource contextSource(ApacheDSContainer ldapContainer) { + return new DefaultSpringSecurityContextSource( + "ldap://127.0.0.1:" + ldapContainer.getPort() + "/dc=spring%20framework,dc=org"); + } + + @Override + public void destroy() { + this.container.stop(); + } + + } + +} diff --git a/ldap/src/integration-test/resources/test-server-with-spaces.ldif b/ldap/src/integration-test/resources/test-server-with-spaces.ldif new file mode 100644 index 0000000000..495ee3f4c0 --- /dev/null +++ b/ldap/src/integration-test/resources/test-server-with-spaces.ldif @@ -0,0 +1,14 @@ +dn: ou=space cadets,dc=spring framework,dc=org +objectclass: top +objectclass: organizationalUnit +ou: space cadets + +dn: uid=space cadet,ou=space cadets,dc=spring framework,dc=org +objectclass: top +objectclass: person +objectclass: organizationalPerson +objectclass: inetOrgPerson +cn: Space Cadet +sn: Cadet +uid: space cadet +userPassword: spacecadetspassword diff --git a/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java b/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java index f9790d606d..7e95ac66b8 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java +++ b/ldap/src/main/java/org/springframework/security/ldap/DefaultSpringSecurityContextSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2021 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. @@ -15,6 +15,10 @@ */ package org.springframework.security.ldap; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Hashtable; import java.util.List; @@ -85,7 +89,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { } setUrls(urls.toArray(new String[0])); - setBase(this.rootDn); + setBase((this.rootDn != null) ? decodeUrl(this.rootDn) : null); setPooled(true); setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy() { @Override @@ -150,7 +154,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { Assert.notNull(baseDn, "The Base DN for the LDAP server must not be null."); Assert.notEmpty(urls, "At least one LDAP server URL must be provided."); - String trimmedBaseDn = baseDn.trim(); + String encodedBaseDn = encodeUrl(baseDn.trim()); StringBuilder providerUrl = new StringBuilder(); for (String serverUrl : urls) { @@ -163,7 +167,7 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { if (!trimmedUrl.endsWith("/")) { providerUrl.append("/"); } - providerUrl.append(trimmedBaseDn); + providerUrl.append(encodedBaseDn); providerUrl.append(" "); } @@ -171,4 +175,22 @@ public class DefaultSpringSecurityContextSource extends LdapContextSource { } + private static String encodeUrl(String url) { + try { + return URLEncoder.encode(url, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); + } + } + + private String decodeUrl(String url) { + try { + return URLDecoder.decode(url, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + throw new IllegalStateException(ex); + } + } + } From 898bdeb0fd7578f1f9bb2828b919f70ea7de3d72 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Wed, 2 Jun 2021 12:19:36 +0300 Subject: [PATCH 262/348] Fix Resource Server clock skew default value in docs Closes gh-6611 --- .../asciidoc/_includes/reactive/oauth2/resource-server.adoc | 2 +- .../_includes/servlet/oauth2/oauth2-resourceserver.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc index dc249e34d3..edb4ed699e 100644 --- a/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/reactive/oauth2/resource-server.adoc @@ -510,7 +510,7 @@ ReactiveJwtDecoder jwtDecoder() { ---- [NOTE] -By default, Resource Server configures a clock skew of 30 seconds. +By default, Resource Server configures a clock skew of 60 seconds. [[webflux-oauth2resourceserver-validation-custom]] ==== Configuring a Custom Validator diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc index db7a546e2b..080ee953d2 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc @@ -531,7 +531,7 @@ JwtDecoder jwtDecoder() { ---- [NOTE] -By default, Resource Server configures a clock skew of 30 seconds. +By default, Resource Server configures a clock skew of 60 seconds. [[oauth2resourceserver-jwt-validation-custom]] ==== Configuring a Custom Validator From d5062bb8281d2f74446c9692723bef065489c564 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Fri, 28 May 2021 12:18:15 -0600 Subject: [PATCH 263/348] PayloadInterceptorRSocket retains all payloads Flux#skip discards its corresponding elements, meaning that they aren't intended for reuse. When using RSocket's ByteBufPayloads, this means that the bytes are releaseed back into RSocket's pool. Since the downstream request may still need the skipped payload, we should construct the publisher in a different way so as to avoid the preemptive release. Deferring Spring JavaFormat to clarify what changed. Closes gh-9345 --- .../core/PayloadInterceptorRSocket.java | 11 ++-- .../core/PayloadInterceptorRSocketTests.java | 62 ++++++++++++++++++- 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java index 418fb67121..0c146ea9d2 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocket.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2019-2021 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. @@ -104,15 +104,18 @@ class PayloadInterceptorRSocket extends RSocketProxy implements ResponderRSocket return intercept(PayloadExchangeType.REQUEST_CHANNEL, firstPayload) .flatMapMany(context -> innerFlux - .skip(1) - .flatMap(p -> intercept(PayloadExchangeType.PAYLOAD, p).thenReturn(p)) - .transform(securedPayloads -> Flux.concat(Flux.just(firstPayload), securedPayloads)) + .index() + .concatMap(tuple -> justOrIntercept(tuple.getT1(), tuple.getT2())) .transform(securedPayloads -> this.source.requestChannel(securedPayloads)) .subscriberContext(context) ); }); } + private Mono justOrIntercept(Long index, Payload payload) { + return (index == 0) ? Mono.just(payload) : intercept(PayloadExchangeType.PAYLOAD, payload).thenReturn(payload); + } + @Override public Mono metadataPush(Payload payload) { return intercept(PayloadExchangeType.METADATA_PUSH, payload) diff --git a/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocketTests.java b/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocketTests.java index a925ac676e..fa149e453a 100644 --- a/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocketTests.java +++ b/rsocket/src/test/java/org/springframework/security/rsocket/core/PayloadInterceptorRSocketTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 the original author or authors. + * Copyright 2019-2021 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. @@ -19,6 +19,8 @@ package org.springframework.security.rsocket.core; import io.rsocket.Payload; import io.rsocket.RSocket; import io.rsocket.metadata.WellKnownMimeType; +import io.rsocket.util.ByteBufPayload; +import io.rsocket.util.DefaultPayload; import io.rsocket.util.RSocketProxy; import org.junit.Test; import org.junit.runner.RunWith; @@ -28,7 +30,9 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.reactivestreams.Publisher; +import org.reactivestreams.Subscription; import org.springframework.http.MediaType; +import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; @@ -41,6 +45,8 @@ import org.springframework.security.rsocket.core.DefaultPayloadExchange; import org.springframework.security.rsocket.core.PayloadInterceptorRSocket; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; +import reactor.util.context.Context; +import reactor.core.CoreSubscriber; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -50,10 +56,13 @@ import reactor.test.publisher.TestPublisher; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -315,6 +324,57 @@ public class PayloadInterceptorRSocketTests { verify(this.delegate).requestChannel(any()); } + // gh-9345 + @Test + public void requestChannelWhenInterceptorCompletesThenAllPayloadsRetained() { + ExecutorService executors = Executors.newSingleThreadExecutor(); + Payload payload = ByteBufPayload.create("data"); + Payload payloadTwo = ByteBufPayload.create("moredata"); + Payload payloadThree = ByteBufPayload.create("stillmoredata"); + Context ctx = Context.empty(); + Flux payloads = this.payloadResult.flux(); + when(this.interceptor.intercept(any(), any())).thenReturn(Mono.empty()) + .thenReturn(Mono.error(() -> new AccessDeniedException("Access Denied"))); + when(this.delegate.requestChannel(any())).thenAnswer((invocation) -> { + Flux input = invocation.getArgument(0); + return Flux.from(input).switchOnFirst((signal, innerFlux) -> innerFlux.map(Payload::getDataUtf8) + .transform((data) -> Flux.create((emitter) -> { + Runnable run = () -> data.subscribe(new CoreSubscriber() { + @Override + public void onSubscribe(Subscription s) { + s.request(3); + } + + @Override + public void onNext(String s) { + emitter.next(s); + } + + @Override + public void onError(Throwable t) { + emitter.error(t); + } + + @Override + public void onComplete() { + emitter.complete(); + } + }); + executors.execute(run); + })).map(DefaultPayload::create)); + }); + PayloadInterceptorRSocket interceptor = new PayloadInterceptorRSocket(this.delegate, + Arrays.asList(this.interceptor), this.metadataMimeType, this.dataMimeType, ctx); + StepVerifier.create(interceptor.requestChannel(payloads).doOnDiscard(Payload.class, Payload::release)) + .then(() -> this.payloadResult.assertSubscribers()) + .then(() -> this.payloadResult.emit(payload, payloadTwo, payloadThree)) + .assertNext((next) -> assertThat(next.getDataUtf8()).isEqualTo(payload.getDataUtf8())) + .verifyError(AccessDeniedException.class); + verify(this.interceptor, times(2)).intercept(this.exchange.capture(), any()); + assertThat(this.exchange.getValue().getPayload()).isEqualTo(payloadTwo); + verify(this.delegate).requestChannel(any()); + } + @Test public void requestChannelWhenInterceptorErrorsThenDelegateNotSubscribed() { RuntimeException expected = new RuntimeException("Oops"); From c0200512a7bd05faf5de5bc504a400b5eb2f998f Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Fri, 21 May 2021 15:03:10 -0500 Subject: [PATCH 264/348] URL encode client credentials Closes gh-9610 --- ...2AuthorizationGrantRequestEntityUtils.java | 23 +++++++++-- ...eAuthorizationCodeTokenResponseClient.java | 23 +++++++++-- ...eClientCredentialsTokenResponseClient.java | 26 +++++++++--- ...ntReactivePasswordTokenResponseClient.java | 26 +++++++++--- ...activeRefreshTokenTokenResponseClient.java | 26 +++++++++--- ...tialsGrantRequestEntityConverterTests.java | 40 +++++++++++++++++++ ...ntCredentialsTokenResponseClientTests.java | 36 ++++++++++++++++- 7 files changed, 177 insertions(+), 23 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java index a1ed924307..20de672a70 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -15,6 +15,11 @@ */ package org.springframework.security.oauth2.client.endpoint; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; + import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -22,8 +27,6 @@ import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import java.util.Collections; - import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; /** @@ -44,11 +47,23 @@ final class OAuth2AuthorizationGrantRequestEntityUtils { HttpHeaders headers = new HttpHeaders(); headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); + String clientId = encodeClientCredential(clientRegistration.getClientId()); + String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); + headers.setBasicAuth(clientId, clientSecret); } return headers; } + private static String encodeClientCredential(String clientCredential) { + try { + return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + // Will not happen since UTF-8 is a standard charset + throw new IllegalArgumentException(ex); + } + } + private static HttpHeaders getDefaultTokenRequestHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java index 76d49cc367..11833b0129 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java @@ -15,6 +15,12 @@ */ package org.springframework.security.oauth2.client.endpoint; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +import reactor.core.publisher.Mono; + import org.springframework.http.MediaType; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -24,10 +30,9 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; +import org.springframework.util.Assert; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.util.Assert; -import reactor.core.publisher.Mono; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -74,7 +79,9 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient implements Re .accept(MediaType.APPLICATION_JSON) .headers(headers -> { if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); + String clientId = encodeClientCredential(clientRegistration.getClientId()); + String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); + headers.setBasicAuth(clientId, clientSecret); } }) .body(body) @@ -91,6 +98,16 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient implements Re }); } + private static String encodeClientCredential(String clientCredential) { + try { + return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + // Will not happen since UTF-8 is a standard charset + throw new IllegalArgumentException(ex); + } + } + private static BodyInserters.FormInserter body(OAuth2AuthorizationExchange authorizationExchange, ClientRegistration clientRegistration) { OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse(); BodyInserters.FormInserter body = BodyInserters diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java index 6acfd38547..af7fa64478 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java @@ -15,6 +15,14 @@ */ package org.springframework.security.oauth2.client.endpoint; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Set; +import java.util.function.Consumer; + +import reactor.core.publisher.Mono; + import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -30,10 +38,6 @@ import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; -import reactor.core.publisher.Mono; - -import java.util.Set; -import java.util.function.Consumer; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -98,11 +102,23 @@ public class WebClientReactiveClientCredentialsTokenResponseClient implements Re return headers -> { headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); + String clientId = encodeClientCredential(clientRegistration.getClientId()); + String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); + headers.setBasicAuth(clientId, clientSecret); } }; } + private static String encodeClientCredential(String clientCredential) { + try { + return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + // Will not happen since UTF-8 is a standard charset + throw new IllegalArgumentException(ex); + } + } + private static BodyInserters.FormInserter body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); BodyInserters.FormInserter body = BodyInserters diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java index 41fe121694..957c9d5508 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java @@ -15,6 +15,14 @@ */ package org.springframework.security.oauth2.client.endpoint; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.function.Consumer; + +import reactor.core.publisher.Mono; + import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -32,10 +40,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.util.Collections; -import java.util.function.Consumer; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -100,11 +104,23 @@ public final class WebClientReactivePasswordTokenResponseClient implements React headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); + String clientId = encodeClientCredential(clientRegistration.getClientId()); + String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); + headers.setBasicAuth(clientId, clientSecret); } }; } + private static String encodeClientCredential(String clientCredential) { + try { + return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + // Will not happen since UTF-8 is a standard charset + throw new IllegalArgumentException(ex); + } + } + private static BodyInserters.FormInserter tokenRequestBody(OAuth2PasswordGrantRequest passwordGrantRequest) { ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration(); BodyInserters.FormInserter body = BodyInserters.fromFormData( diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java index 6d6daa83d5..abb200a9c1 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java @@ -15,6 +15,14 @@ */ package org.springframework.security.oauth2.client.endpoint; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.function.Consumer; + +import reactor.core.publisher.Mono; + import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -32,10 +40,6 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Mono; - -import java.util.Collections; -import java.util.function.Consumer; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -88,11 +92,23 @@ public final class WebClientReactiveRefreshTokenTokenResponseClient implements R headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); + String clientId = encodeClientCredential(clientRegistration.getClientId()); + String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); + headers.setBasicAuth(clientId, clientSecret); } }; } + private static String encodeClientCredential(String clientCredential) { + try { + return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); + } + catch (UnsupportedEncodingException ex) { + // Will not happen since UTF-8 is a standard charset + throw new IllegalArgumentException(ex); + } + } + private static BodyInserters.FormInserter tokenRequestBody(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) { ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration(); BodyInserters.FormInserter body = BodyInserters.fromFormData( diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java index 28233c9a16..0f24312af5 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java @@ -15,13 +15,20 @@ */ package org.springframework.security.oauth2.client.endpoint; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + import org.junit.Before; import org.junit.Test; + import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.registration.ClientRegistration; +import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; @@ -74,4 +81,37 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("read write"); } + + // gh-9610 + @SuppressWarnings("unchecked") + @Test + public void convertWhenSpecialCharactersThenConvertsWithEncodedClientCredentials() + throws UnsupportedEncodingException { + String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ "; + // @formatter:off + ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() + .clientId(clientCredentialWithAnsiKeyboardSpecialCharacters) + .clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters) + .build(); + // @formatter:on + OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( + clientRegistration); + RequestEntity requestEntity = this.converter.convert(clientCredentialsGrantRequest); + assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); + assertThat(requestEntity.getUrl().toASCIIString()) + .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); + HttpHeaders headers = requestEntity.getHeaders(); + assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); + assertThat(headers.getContentType()) + .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); + String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters, + StandardCharsets.UTF_8.toString()); + String clientCredentials = Base64.getEncoder().encodeToString( + (urlEncodedClientCredential + ":" + urlEncodedClientCredential).getBytes(StandardCharsets.UTF_8)); + assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials); + MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); + assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) + .isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); + assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes()); + } } 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 c4d92d629c..430efd6927 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -16,12 +16,17 @@ package org.springframework.security.oauth2.client.endpoint; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.junit.After; import org.junit.Before; import org.junit.Test; + import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.client.registration.ClientRegistration; @@ -82,6 +87,35 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests { assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser"); } + // gh-9610 + @Test + public void getTokenResponseWhenSpecialCharactersThenSuccessWithEncodedClientCredentials() throws Exception { + // @formatter:off + enqueueJson("{\n" + + " \"access_token\":\"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3\",\n" + + " \"token_type\":\"bearer\",\n" + + " \"expires_in\":3600,\n" + + " \"refresh_token\":\"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk\",\n" + + " \"scope\":\"create\"\n" + + "}"); + // @formatter:on + String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ "; + OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest( + this.clientRegistration.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters) + .clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters).build()); + OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block(); + RecordedRequest actualRequest = this.server.takeRequest(); + String body = actualRequest.getBody().readUtf8(); + assertThat(response.getAccessToken()).isNotNull(); + String urlEncodedClientCredentialecret = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters, + StandardCharsets.UTF_8.toString()); + String clientCredentials = Base64.getEncoder() + .encodeToString((urlEncodedClientCredentialecret + ":" + urlEncodedClientCredentialecret) + .getBytes(StandardCharsets.UTF_8)); + assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials); + assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser"); + } + @Test public void getTokenResponseWhenPostThenSuccess() throws Exception { ClientRegistration registration = this.clientRegistration From c41aeed6cbb1ccd2d5c1e4628663e879f1e578c1 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 8 Jun 2021 13:35:00 -0600 Subject: [PATCH 265/348] Fix Getting Started Link Closes gh-6502 --- README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.adoc b/README.adoc index 79feb8370b..73e398e181 100644 --- a/README.adoc +++ b/README.adoc @@ -19,7 +19,7 @@ Be sure to read the https://docs.spring.io/spring-security/site/docs/current/ref Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation]. == Quick Start -We recommend you visit https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/[Spring Security Reference] and read the "Getting Started" page. +See https://docs.spring.io/spring-security/site/docs/5.2.x/reference/html5/#servlet-hello[Hello Spring Security] to get started with a "Hello, World" application. == Building from Source Spring Security uses a https://gradle.org[Gradle]-based build system. From 08f7a97ae738453f38ed02789f53c7aa2517e31a Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 8 Jun 2021 16:10:24 -0600 Subject: [PATCH 266/348] Anonymous Authentication Argument Resolution Docs Closes gh-3338 --- .../servlet/authentication/anonymous.adoc | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/anonymous.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/anonymous.adoc index 9f2508f7aa..e07def7e0a 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/anonymous.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/authentication/anonymous.adoc @@ -100,3 +100,35 @@ This is an example of the use of the `AuthenticatedVoter` which we will see in t It uses an `AuthenticationTrustResolver` to process this particular configuration attribute and grant access to anonymous users. The `AuthenticatedVoter` approach is more powerful, since it allows you to differentiate between anonymous, remember-me and fully-authenticated users. If you don't need this functionality though, then you can stick with `ROLE_ANONYMOUS`, which will be processed by Spring Security's standard `RoleVoter`. + +[[anonymous-auth-mvc-controller]] +=== Getting Anonymous Authentications with Spring MVC + +https://docs.spring.io/spring-framework/docs/5.2.x/spring-framework-reference/web.html#mvc-ann-arguments[Spring MVC resolves parameters of type `Principal`] using its own argument resolver. + +This means that a construct like this one: + +[source,java] +---- +@GetMapping("/") +public String method(Authentication authentication) { + if (authentication instanceof AnonymousAuthenticationToken) { + return "anonymous"; + } else { + return "not anonymous"; + } +} +---- + +will always return "not anonymous", even for anonymous requests. +The reason is that Spring MVC resolves the parameter using `HttpServletRequest#getPrincipal`, which is `null` when the request is anonymous. + +If you'd like to obtain the `Authentication` in anonymous requests, use `@CurrentSecurityContext` instead: + +[source,java] +---- +@GetMapping("/") +public String method(@CurrentSecurityContext SecurityContext context) { + return context.getAuthentication().getName(); +} +---- From 5a4cfe1226088f19e39940172626b0cdd00155d2 Mon Sep 17 00:00:00 2001 From: Marcus Hert da Coregio Date: Fri, 11 Jun 2021 11:03:29 -0300 Subject: [PATCH 267/348] Fix Adding Filter Relative to Custom Filter Closes gh-9787 --- .../web/builders/FilterOrderRegistration.java | 16 +- .../annotation/web/builders/HttpSecurity.java | 1 + .../FilterOrderRegistrationTests.java | 75 ++++++++++ .../builders/HttpSecurityAddFilterTest.java | 138 +++++++++++++++++- 4 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java index e37c14ae66..b1467457a8 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2021 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. @@ -108,9 +108,19 @@ final class FilterOrderRegistration { put(SwitchUserFilter.class, order.next()); } - private void put(Class filter, int position) { + /** + * Register a {@link Filter} with its specific position. If the {@link Filter} was + * already registered before, the position previously defined is not going to be + * overriden + * @param filter the {@link Filter} to register + * @param position the position to associate with the {@link Filter} + */ + void put(Class filter, int position) { String className = filter.getName(); - filterToOrder.put(className, position); + if (this.filterToOrder.containsKey(className)) { + return; + } + this.filterToOrder.put(className, position); } /** diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java index 31a0e8686a..dec4442186 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java @@ -2600,6 +2600,7 @@ public final class HttpSecurity extends private HttpSecurity addFilterAtOffsetOf(Filter filter, int offset, Class registeredFilter) { int order = this.filterOrders.getOrder(registeredFilter) + offset; this.filters.add(new OrderedFilter(filter, order)); + this.filterOrders.put(filter.getClass(), order); return this; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java new file mode 100644 index 0000000000..2e102a2126 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2021 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.config.annotation.web.builders; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Test; + +import org.springframework.security.web.access.channel.ChannelProcessingFilter; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FilterOrderRegistrationTests { + + private final FilterOrderRegistration filterOrderRegistration = new FilterOrderRegistration(); + + @Test + public void putWhenNewFilterThenInsertCorrect() { + int position = 153; + this.filterOrderRegistration.put(MyFilter.class, position); + Integer order = this.filterOrderRegistration.getOrder(MyFilter.class); + assertThat(order).isEqualTo(position); + } + + @Test + public void putWhenCustomFilterAlreadyExistsThenDoesNotOverride() { + int position = 160; + this.filterOrderRegistration.put(MyFilter.class, position); + this.filterOrderRegistration.put(MyFilter.class, 173); + Integer order = this.filterOrderRegistration.getOrder(MyFilter.class); + assertThat(order).isEqualTo(position); + } + + @Test + public void putWhenPredefinedFilterThenDoesNotOverride() { + int position = 100; + Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class); + this.filterOrderRegistration.put(MyFilter.class, position); + Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class); + Integer predefinedFilterOrderAfter = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class); + assertThat(myFilterOrder).isEqualTo(position); + assertThat(predefinedFilterOrderAfter).isEqualTo(predefinedFilterOrderBefore).isEqualTo(position); + } + + static class MyFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse); + } + + } + +} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java b/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java index b83818503f..0676c52950 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/builders/HttpSecurityAddFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -37,7 +37,9 @@ import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.access.channel.ChannelProcessingFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; +import org.springframework.security.web.header.HeaderWriterFilter; import static org.assertj.core.api.Assertions.assertThat; @@ -70,6 +72,46 @@ public class HttpSecurityAddFilterTest { ExceptionTranslationFilter.class); } + @Test + public void addFilterAfterWhenAfterCustomFilterThenOrderCorrect() { + this.spring.register(MyOtherFilterRelativeToMyFilterAfterConfig.class).autowire(); + + assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class, + MyOtherFilter.class); + } + + @Test + public void addFilterBeforeWhenBeforeCustomFilterThenOrderCorrect() { + this.spring.register(MyOtherFilterRelativeToMyFilterBeforeConfig.class).autowire(); + + assertThatFilters().containsSubsequence(MyOtherFilter.class, MyFilter.class, + WebAsyncManagerIntegrationFilter.class); + } + + @Test + public void addFilterAtWhenAtCustomFilterThenOrderCorrect() { + this.spring.register(MyOtherFilterRelativeToMyFilterAtConfig.class).autowire(); + + assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyFilter.class, + MyOtherFilter.class, SecurityContextPersistenceFilter.class); + } + + @Test + public void addFilterBeforeWhenCustomFilterDifferentPlacesThenOrderCorrect() { + this.spring.register(MyOtherFilterBeforeToMyFilterMultipleAfterConfig.class).autowire(); + + assertThatFilters().containsSubsequence(WebAsyncManagerIntegrationFilter.class, MyOtherFilter.class, + MyFilter.class, ExceptionTranslationFilter.class); + } + + @Test + public void addFilterBeforeAndAfterWhenCustomFiltersDifferentPlacesThenOrderCorrect() { + this.spring.register(MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig.class).autowire(); + + assertThatFilters().containsSubsequence(HeaderWriterFilter.class, MyFilter.class, MyOtherFilter.class, + MyOtherFilter.class, MyAnotherFilter.class, MyFilter.class, ExceptionTranslationFilter.class); + } + private ListAssert> assertThatFilters() { FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); List> filters = filterChain.getFilters("/").stream().map(Object::getClass) @@ -87,6 +129,26 @@ public class HttpSecurityAddFilterTest { } + static class MyOtherFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse); + } + + } + + static class MyAnotherFilter implements Filter { + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + filterChain.doFilter(servletRequest, servletResponse); + } + + } + @EnableWebSecurity static class MyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter { @@ -129,4 +191,78 @@ public class HttpSecurityAddFilterTest { } + @EnableWebSecurity + static class MyOtherFilterRelativeToMyFilterAfterConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class) + .addFilterAfter(new MyOtherFilter(), MyFilter.class); + // @formatter:on + } + + } + + @EnableWebSecurity + static class MyOtherFilterRelativeToMyFilterBeforeConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterBefore(new MyFilter(), WebAsyncManagerIntegrationFilter.class) + .addFilterBefore(new MyOtherFilter(), MyFilter.class); + // @formatter:on + } + + } + + @EnableWebSecurity + static class MyOtherFilterRelativeToMyFilterAtConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterAt(new MyFilter(), WebAsyncManagerIntegrationFilter.class) + .addFilterAt(new MyOtherFilter(), MyFilter.class); + // @formatter:on + } + + } + + @EnableWebSecurity + static class MyOtherFilterBeforeToMyFilterMultipleAfterConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class) + .addFilterAfter(new MyFilter(), ExceptionTranslationFilter.class) + .addFilterBefore(new MyOtherFilter(), MyFilter.class); + // @formatter:on + } + + } + + @EnableWebSecurity + static class MyAnotherFilterRelativeToMyCustomFiltersMultipleConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .addFilterAfter(new MyFilter(), HeaderWriterFilter.class) + .addFilterBefore(new MyOtherFilter(), ExceptionTranslationFilter.class) + .addFilterAfter(new MyOtherFilter(), MyFilter.class) + .addFilterAt(new MyAnotherFilter(), MyOtherFilter.class) + .addFilterAfter(new MyFilter(), MyAnotherFilter.class); + // @formatter:on + } + + } + } From 67a18f564a2e17540bafb3feb6744a47055438b5 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg <5248162+sjohnr@users.noreply.github.com> Date: Tue, 15 Jun 2021 11:03:30 -0500 Subject: [PATCH 268/348] Store one request by default in WebSessionOAuth2ServerAuthorizationRequestRepository Related to gh-9649 Closes gh-9857 --- ...2ServerAuthorizationRequestRepository.java | 127 +++++---- ...lowMultipleAuthorizationRequestsTests.java | 252 ++++++++++++++++++ ...lowMultipleAuthorizationRequestsTests.java | 159 +++++++++++ ...erAuthorizationRequestRepositoryTests.java | 141 +--------- 4 files changed, 498 insertions(+), 181 deletions(-) create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepository.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepository.java index 2ad3026ca4..3ca3b3c881 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepository.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -34,6 +34,7 @@ import reactor.core.publisher.Mono; * {@link OAuth2AuthorizationRequest} in the {@code WebSession}. * * @author Rob Winch + * @author Steve Riesenberg * @since 5.1 * @see AuthorizationRequestRepository * @see OAuth2AuthorizationRequest @@ -46,6 +47,8 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository private final String sessionAttributeName = DEFAULT_AUTHORIZATION_REQUEST_ATTR_NAME; + private boolean allowMultipleAuthorizationRequests; + @Override public Mono loadAuthorizationRequest( ServerWebExchange exchange) { @@ -53,17 +56,33 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository if (state == null) { return Mono.empty(); } - return getStateToAuthorizationRequest(exchange) - .filter(stateToAuthorizationRequest -> stateToAuthorizationRequest.containsKey(state)) - .map(stateToAuthorizationRequest -> stateToAuthorizationRequest.get(state)); + // @formatter:off + return this.getSessionAttributes(exchange) + .filter((sessionAttrs) -> sessionAttrs.containsKey(this.sessionAttributeName)) + .map(this::getAuthorizationRequests) + .filter((stateToAuthorizationRequest) -> stateToAuthorizationRequest.containsKey(state)) + .map((stateToAuthorizationRequest) -> stateToAuthorizationRequest.get(state)); + // @formatter:on } @Override public Mono saveAuthorizationRequest( OAuth2AuthorizationRequest authorizationRequest, ServerWebExchange exchange) { Assert.notNull(authorizationRequest, "authorizationRequest cannot be null"); - return saveStateToAuthorizationRequest(exchange) - .doOnNext(stateToAuthorizationRequest -> stateToAuthorizationRequest.put(authorizationRequest.getState(), authorizationRequest)) + Assert.notNull(exchange, "exchange cannot be null"); + // @formatter:off + return getSessionAttributes(exchange) + .doOnNext((sessionAttrs) -> { + if (this.allowMultipleAuthorizationRequests) { + Map authorizationRequests = this.getAuthorizationRequests( + sessionAttrs); + authorizationRequests.put(authorizationRequest.getState(), authorizationRequest); + sessionAttrs.put(this.sessionAttributeName, authorizationRequests); + } + else { + sessionAttrs.put(this.sessionAttributeName, authorizationRequest); + } + }) .then(); } @@ -74,27 +93,24 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository if (state == null) { return Mono.empty(); } - return exchange.getSession() - .map(WebSession::getAttributes) - .handle((sessionAttrs, sink) -> { - Map stateToAuthzRequest = sessionAttrsMapStateToAuthorizationRequest(sessionAttrs); - if (stateToAuthzRequest == null) { - sink.complete(); - return; - } - OAuth2AuthorizationRequest removedValue = stateToAuthzRequest.remove(state); - if (stateToAuthzRequest.isEmpty()) { - sessionAttrs.remove(this.sessionAttributeName); - } else if (removedValue != null) { - // gh-7327 Overwrite the existing Map to ensure the state is saved for distributed sessions - sessionAttrs.put(this.sessionAttributeName, stateToAuthzRequest); - } - if (removedValue == null) { - sink.complete(); - } else { - sink.next(removedValue); - } - }); + // @formatter:off + return getSessionAttributes(exchange) + .flatMap((sessionAttrs) -> { + Map authorizationRequests = this.getAuthorizationRequests( + sessionAttrs); + OAuth2AuthorizationRequest originalRequest = authorizationRequests.remove(state); + if (authorizationRequests.isEmpty()) { + sessionAttrs.remove(this.sessionAttributeName); + } + else if (authorizationRequests.size() == 1) { + sessionAttrs.put(this.sessionAttributeName, authorizationRequests.values().iterator().next()); + } + else { + sessionAttrs.put(this.sessionAttributeName, authorizationRequests); + } + return Mono.justOrEmpty(originalRequest); + }); + // @formatter:on } /** @@ -111,31 +127,40 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository return exchange.getSession().map(WebSession::getAttributes); } - private Mono> getStateToAuthorizationRequest(ServerWebExchange exchange) { - Assert.notNull(exchange, "exchange cannot be null"); - - return getSessionAttributes(exchange) - .flatMap(sessionAttrs -> Mono.justOrEmpty(this.sessionAttrsMapStateToAuthorizationRequest(sessionAttrs))); + private Map getAuthorizationRequests(Map sessionAttrs) { + Object sessionAttributeValue = sessionAttrs.get(this.sessionAttributeName); + if (sessionAttributeValue == null) { + return new HashMap<>(); + } + else if (sessionAttributeValue instanceof OAuth2AuthorizationRequest) { + OAuth2AuthorizationRequest oauth2AuthorizationRequest = (OAuth2AuthorizationRequest) sessionAttributeValue; + Map authorizationRequests = new HashMap<>(1); + authorizationRequests.put(oauth2AuthorizationRequest.getState(), oauth2AuthorizationRequest); + return authorizationRequests; + } + else if (sessionAttributeValue instanceof Map) { + @SuppressWarnings("unchecked") + Map authorizationRequests = (Map) sessionAttrs + .get(this.sessionAttributeName); + return authorizationRequests; + } + else { + throw new IllegalStateException( + "authorizationRequests is supposed to be a Map or OAuth2AuthorizationRequest but actually is a " + + sessionAttributeValue.getClass()); + } } - private Mono> saveStateToAuthorizationRequest(ServerWebExchange exchange) { - Assert.notNull(exchange, "exchange cannot be null"); - - return getSessionAttributes(exchange) - .doOnNext(sessionAttrs -> { - Object stateToAuthzRequest = sessionAttrs.get(this.sessionAttributeName); - - if (stateToAuthzRequest == null) { - stateToAuthzRequest = new HashMap(); - } - - // No matter stateToAuthzRequest was in session or not, we should always put it into session again - // in case of redis or hazelcast session. #6215 - sessionAttrs.put(this.sessionAttributeName, stateToAuthzRequest); - }).flatMap(sessionAttrs -> Mono.justOrEmpty(this.sessionAttrsMapStateToAuthorizationRequest(sessionAttrs))); - } - - private Map sessionAttrsMapStateToAuthorizationRequest(Map sessionAttrs) { - return (Map) sessionAttrs.get(this.sessionAttributeName); + /** + * Configure if multiple {@link OAuth2AuthorizationRequest}s should be stored per + * session. Default is false (not allow multiple {@link OAuth2AuthorizationRequest} + * per session). + * @param allowMultipleAuthorizationRequests true allows more than one + * {@link OAuth2AuthorizationRequest} to be stored per session. + * @since 5.5 + */ + @Deprecated + public void setAllowMultipleAuthorizationRequests(boolean allowMultipleAuthorizationRequests) { + this.allowMultipleAuthorizationRequests = allowMultipleAuthorizationRequests; } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java new file mode 100644 index 0000000000..5b3a014bf6 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests.java @@ -0,0 +1,252 @@ +/* + * Copyright 2002-2021 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.client.web.server; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import org.springframework.web.server.adapter.DefaultServerWebExchange; +import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; +import org.springframework.web.server.session.WebSessionManager; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link WebSessionOAuth2ServerAuthorizationRequestRepository} when + * {@link WebSessionOAuth2ServerAuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)} + * is enabled. + * + * @author Steve Riesenberg + */ + +public class WebSessionOAuth2ServerAuthorizationRequestRepositoryAllowMultipleAuthorizationRequestsTests + extends WebSessionOAuth2ServerAuthorizationRequestRepositoryTests { + + @Before + public void setup() { + this.repository = new WebSessionOAuth2ServerAuthorizationRequestRepository(); + this.repository.setAllowMultipleAuthorizationRequests(true); + } + + @Test + public void loadAuthorizationRequestWhenMultipleSavedThenAuthorizationRequest() { + String oldState = "state0"; + // @formatter:off + MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, oldState) + .build(); + OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(oldState) + .build(); + // @formatter:on + WebSessionManager sessionManager = (e) -> this.exchange.getSession(); + this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + // @formatter:off + Mono saveAndSaveAndLoad = this.repository + .saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) + .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) + .then(this.repository.loadAuthorizationRequest(oldExchange)); + StepVerifier.create(saveAndSaveAndLoad) + .expectNext(oldAuthorizationRequest) + .verifyComplete(); + StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) + .expectNext(this.authorizationRequest) + .verifyComplete(); + // @formatter:on + } + + // gh-5145 + @Test + public void loadAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequestsThenReturnOldAuthorizationRequest() { + // save 2 requests with legacy (allowMultipleAuthorizationRequests=true) and load + // with new + WebSessionOAuth2ServerAuthorizationRequestRepository legacy = new WebSessionOAuth2ServerAuthorizationRequestRepository(); + legacy.setAllowMultipleAuthorizationRequests(true); + // @formatter:off + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state1) + .build(); + StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest1, this.exchange)) + .verifyComplete(); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state2) + .build(); + StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest2, this.exchange)) + .verifyComplete(); + ServerHttpRequest newRequest = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, state1) + .build(); + ServerWebExchange newExchange = this.exchange.mutate() + .request(newRequest) + .build(); + StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange)) + .expectNext(authorizationRequest1) + .verifyComplete(); + // @formatter:on + } + + // gh-5145 + @Test + public void saveAuthorizationRequestWhenSavedWithAllowMultipleAuthorizationRequestsThenLoadNewAuthorizationRequest() { + // save 2 requests with legacy (allowMultipleAuthorizationRequests=true), save + // with new, and load with new + WebSessionOAuth2ServerAuthorizationRequestRepository legacy = new WebSessionOAuth2ServerAuthorizationRequestRepository(); + legacy.setAllowMultipleAuthorizationRequests(true); + // @formatter:off + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state1) + .build(); + StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest1, this.exchange)) + .verifyComplete(); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state2) + .build(); + StepVerifier.create(legacy.saveAuthorizationRequest(authorizationRequest2, this.exchange)) + .verifyComplete(); + String state3 = "state-5566"; + OAuth2AuthorizationRequest authorizationRequest3 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state3) + .build(); + ServerHttpRequest newRequest = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, state3) + .build(); + ServerWebExchange newExchange = this.exchange.mutate() + .request(newRequest) + .build(); + Mono saveAndLoad = this.repository + .saveAuthorizationRequest(authorizationRequest3, this.exchange) + .then(this.repository.loadAuthorizationRequest(newExchange)); + StepVerifier.create(saveAndLoad) + .expectNext(authorizationRequest3) + .verifyComplete(); + // @formatter:on + } + + @Test + public void removeAuthorizationRequestWhenMultipleThenOnlyOneRemoved() { + String oldState = "state0"; + // @formatter:off + MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, oldState) + .build(); + OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(oldState) + .build(); + // @formatter:on + WebSessionManager sessionManager = (e) -> this.exchange.getSession(); + this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + // @formatter:off + Mono saveAndSaveAndRemove = this.repository + .saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) + .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) + .then(this.repository.removeAuthorizationRequest(this.exchange)); + StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest) + .verifyComplete(); + StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) + .verifyComplete(); + StepVerifier.create(this.repository.loadAuthorizationRequest(oldExchange)) + .expectNext(oldAuthorizationRequest) + .verifyComplete(); + // @formatter:on + } + + // gh-7327 + @Test + public void removeAuthorizationRequestWhenMultipleThenRemovedAndSessionAttributeUpdated() { + String oldState = "state0"; + // @formatter:off + MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, oldState) + .build(); + OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(oldState) + .build(); + // @formatter:on + Map sessionAttrs = spy(new HashMap<>()); + WebSession session = mock(WebSession.class); + given(session.getAttributes()).willReturn(sessionAttrs); + WebSessionManager sessionManager = (e) -> Mono.just(session); + this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + // @formatter:off + Mono saveAndSaveAndRemove = this.repository + .saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) + .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) + .then(this.repository.removeAuthorizationRequest(this.exchange)); + StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest) + .verifyComplete(); + StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) + .verifyComplete(); + // @formatter:on + verify(sessionAttrs, times(3)).put(any(), any()); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java new file mode 100644 index 0000000000..5e60fd298f --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests.java @@ -0,0 +1,159 @@ +/* + * Copyright 2002-2021 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.client.web.server; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import org.springframework.http.codec.ServerCodecConfigurer; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpResponse; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebSession; +import org.springframework.web.server.adapter.DefaultServerWebExchange; +import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; +import org.springframework.web.server.session.WebSessionManager; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Tests for {@link WebSessionOAuth2ServerAuthorizationRequestRepository} when + * {@link WebSessionOAuth2ServerAuthorizationRequestRepository#setAllowMultipleAuthorizationRequests(boolean)} + * is disabled. + * + * @author Steve Riesenberg + */ +public class WebSessionOAuth2ServerAuthorizationRequestRepositoryDoNotAllowMultipleAuthorizationRequestsTests + extends WebSessionOAuth2ServerAuthorizationRequestRepositoryTests { + + @Before + public void setup() { + this.repository = new WebSessionOAuth2ServerAuthorizationRequestRepository(); + this.repository.setAllowMultipleAuthorizationRequests(false); + } + + // gh-5145 + @Test + public void loadAuthorizationRequestWhenMultipleSavedThenReturnLastAuthorizationRequest() { + // @formatter:off + String state1 = "state-1122"; + OAuth2AuthorizationRequest authorizationRequest1 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state1) + .build(); + StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest1, this.exchange)) + .verifyComplete(); + String state2 = "state-3344"; + OAuth2AuthorizationRequest authorizationRequest2 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state2) + .build(); + StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest2, this.exchange)) + .verifyComplete(); + String state3 = "state-5566"; + OAuth2AuthorizationRequest authorizationRequest3 = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(state3) + .build(); + StepVerifier.create(this.repository.saveAuthorizationRequest(authorizationRequest3, this.exchange)) + .verifyComplete(); + ServerHttpRequest newRequest1 = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, state1) + .build(); + ServerWebExchange newExchange1 = this.exchange.mutate() + .request(newRequest1) + .build(); + StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange1)) + .verifyComplete(); + ServerHttpRequest newRequest2 = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, state2) + .build(); + ServerWebExchange newExchange2 = this.exchange.mutate() + .request(newRequest2) + .build(); + StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange2)) + .verifyComplete(); + ServerHttpRequest newRequest3 = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, state3) + .build(); + ServerWebExchange newExchange3 = this.exchange.mutate() + .request(newRequest3) + .build(); + StepVerifier.create(this.repository.loadAuthorizationRequest(newExchange3)) + .expectNext(authorizationRequest3) + .verifyComplete(); + // @formatter:on + } + + // gh-5145 + @Test + public void removeAuthorizationRequestWhenMultipleThenSessionAttributeRemoved() { + String oldState = "state0"; + // @formatter:off + MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") + .queryParam(OAuth2ParameterNames.STATE, oldState) + .build(); + OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() + .authorizationUri("https://example.com/oauth2/authorize") + .clientId("client-id") + .redirectUri("http://localhost/client-1") + .state(oldState) + .build(); + // @formatter:on + Map sessionAttrs = spy(new HashMap<>()); + WebSession session = mock(WebSession.class); + given(session.getAttributes()).willReturn(sessionAttrs); + WebSessionManager sessionManager = (e) -> Mono.just(session); + this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), + sessionManager, ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); + // @formatter:off + Mono saveAndSaveAndRemove = this.repository + .saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) + .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) + .then(this.repository.removeAuthorizationRequest(this.exchange)); + StepVerifier.create(saveAndSaveAndRemove).expectNext(this.authorizationRequest) + .verifyComplete(); + StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) + .verifyComplete(); + // @formatter:on + verify(sessionAttrs, times(2)).put(anyString(), any()); + verify(sessionAttrs).remove(anyString()); + } + +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryTests.java index b4e11c05be..cfe6388435 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/WebSessionOAuth2ServerAuthorizationRequestRepositoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -16,51 +16,39 @@ package org.springframework.security.oauth2.client.web.server; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import java.util.HashMap; import java.util.Map; import org.junit.Test; -import org.springframework.http.codec.ServerCodecConfigurer; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebSession; -import org.springframework.web.server.adapter.DefaultServerWebExchange; -import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver; -import org.springframework.web.server.session.WebSessionManager; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * @author Rob Winch * @since 5.1 */ -public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests { +public abstract class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests { - private WebSessionOAuth2ServerAuthorizationRequestRepository repository = - new WebSessionOAuth2ServerAuthorizationRequestRepository(); + protected WebSessionOAuth2ServerAuthorizationRequestRepository repository; - private OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() + // @formatter:off + protected OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() .authorizationUri("https://example.com/oauth2/authorize") .clientId("client-id") .redirectUri("http://localhost/client-1") .state("state") .build(); - private ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/") - .queryParam(OAuth2ParameterNames.STATE, "state")); + protected ServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get("/").queryParam(OAuth2ParameterNames.STATE, "state")); @Test public void loadAuthorizationRequestWhenNullExchangeThenIllegalArgumentException() { @@ -106,39 +94,6 @@ public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests { .verifyComplete(); } - @Test - public void loadAuthorizationRequestWhenMultipleSavedThenAuthorizationRequest() { - String oldState = "state0"; - MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") - .queryParam(OAuth2ParameterNames.STATE, oldState).build(); - - OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://example.com/oauth2/authorize") - .clientId("client-id") - .redirectUri("http://localhost/client-1") - .state(oldState) - .build(); - - WebSessionManager sessionManager = e -> this.exchange.getSession(); - - this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager, - ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); - ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager, - ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); - - Mono saveAndSaveAndLoad = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) - .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) - .then(this.repository.loadAuthorizationRequest(oldExchange)); - - StepVerifier.create(saveAndSaveAndLoad) - .expectNext(oldAuthorizationRequest) - .verifyComplete(); - - StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) - .expectNext(this.authorizationRequest) - .verifyComplete(); - } - @Test public void saveAuthorizationRequestWhenAuthorizationRequestNullThenThrowsIllegalArgumentException() { this.authorizationRequest = null; @@ -203,80 +158,6 @@ public class WebSessionOAuth2ServerAuthorizationRequestRepositoryTests { .verifyComplete(); } - @Test - public void removeAuthorizationRequestWhenMultipleThenOnlyOneRemoved() { - String oldState = "state0"; - MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") - .queryParam(OAuth2ParameterNames.STATE, oldState).build(); - - OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://example.com/oauth2/authorize") - .clientId("client-id") - .redirectUri("http://localhost/client-1") - .state(oldState) - .build(); - - WebSessionManager sessionManager = e -> this.exchange.getSession(); - - this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager, - ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); - ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager, - ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); - - Mono saveAndSaveAndRemove = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) - .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) - .then(this.repository.removeAuthorizationRequest(this.exchange)); - - StepVerifier.create(saveAndSaveAndRemove) - .expectNext(this.authorizationRequest) - .verifyComplete(); - - StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) - .verifyComplete(); - - StepVerifier.create(this.repository.loadAuthorizationRequest(oldExchange)) - .expectNext(oldAuthorizationRequest) - .verifyComplete(); - } - - // gh-7327 - @Test - public void removeAuthorizationRequestWhenMultipleThenRemovedAndSessionAttributeUpdated() { - String oldState = "state0"; - MockServerHttpRequest oldRequest = MockServerHttpRequest.get("/") - .queryParam(OAuth2ParameterNames.STATE, oldState).build(); - - OAuth2AuthorizationRequest oldAuthorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://example.com/oauth2/authorize") - .clientId("client-id") - .redirectUri("http://localhost/client-1") - .state(oldState) - .build(); - - Map sessionAttrs = spy(new HashMap<>()); - WebSession session = mock(WebSession.class); - when(session.getAttributes()).thenReturn(sessionAttrs); - WebSessionManager sessionManager = e -> Mono.just(session); - - this.exchange = new DefaultServerWebExchange(this.exchange.getRequest(), new MockServerHttpResponse(), sessionManager, - ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); - ServerWebExchange oldExchange = new DefaultServerWebExchange(oldRequest, new MockServerHttpResponse(), sessionManager, - ServerCodecConfigurer.create(), new AcceptHeaderLocaleContextResolver()); - - Mono saveAndSaveAndRemove = this.repository.saveAuthorizationRequest(oldAuthorizationRequest, oldExchange) - .then(this.repository.saveAuthorizationRequest(this.authorizationRequest, this.exchange)) - .then(this.repository.removeAuthorizationRequest(this.exchange)); - - StepVerifier.create(saveAndSaveAndRemove) - .expectNext(this.authorizationRequest) - .verifyComplete(); - - StepVerifier.create(this.repository.loadAuthorizationRequest(this.exchange)) - .verifyComplete(); - - verify(sessionAttrs, times(3)).put(any(), any()); - } - private void assertSessionStartedIs(boolean expected) { Mono isStarted = this.exchange.getSession().map(WebSession::isStarted); StepVerifier.create(isStarted) From f91608dcba08d48b998422e40a0d7f161f0373b9 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 17 Jun 2021 16:17:27 +0200 Subject: [PATCH 269/348] Disable default logout page when logout disabled Closes gh-9475 --- .../DefaultLoginPageConfigurer.java | 7 ++- .../config/web/server/ServerHttpSecurity.java | 5 +- .../DefaultLoginPageConfigurerTests.java | 52 ++++++++++++++++++- .../config/web/server/LogoutSpecTests.java | 32 ++++++++---- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurer.java index 251c586f3e..2e9212c470 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2021 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. @@ -100,7 +100,10 @@ public final class DefaultLoginPageConfigurer> if (loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) { loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter); http.addFilter(loginPageGeneratingFilter); - http.addFilter(this.logoutPageGeneratingFilter); + LogoutConfigurer logoutConfigurer = http.getConfigurer(LogoutConfigurer.class); + if (logoutConfigurer != null) { + http.addFilter(this.logoutPageGeneratingFilter); + } } } diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index dfc8b3c24e..36c982e23b 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -3266,7 +3266,10 @@ public class ServerHttpSecurity { } if (loginPage != null) { http.addFilterAt(loginPage, SecurityWebFiltersOrder.LOGIN_PAGE_GENERATING); - http.addFilterAt(new LogoutPageGeneratingWebFilter(), SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING); + if (http.logout != null) { + http.addFilterAt(new LogoutPageGeneratingWebFilter(), + SecurityWebFiltersOrder.LOGOUT_PAGE_GENERATING); + } } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java index 0bd6e0417f..a37a2e7a83 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -44,11 +44,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link DefaultLoginPageConfigurer} @@ -217,6 +220,18 @@ public class DefaultLoginPageConfigurerTests { )); } + @Test + public void formLoginWhenLogoutEnabledThenCreatesDefaultLogoutPage() throws Exception { + this.spring.register(DefaultLogoutPageConfig.class).autowire(); + this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isOk()); + } + + @Test + public void formLoginWhenLogoutDisabledThenDefaultLogoutPageDoesNotExist() throws Exception { + this.spring.register(LogoutDisabledConfig.class).autowire(); + this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isNotFound()); + } + @EnableWebSecurity static class DefaultLoginPageConfig extends WebSecurityConfigurerAdapter { @Override @@ -552,6 +567,41 @@ public class DefaultLoginPageConfigurerTests { } } + @EnableWebSecurity + static class DefaultLogoutPageConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authorize) -> authorize + .anyRequest().authenticated() + ) + .formLogin(withDefaults()); + // @formatter:on + } + + } + + @EnableWebSecurity + static class LogoutDisabledConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authorize) -> authorize + .anyRequest().authenticated() + ) + .formLogin(withDefaults()) + .logout((logout) -> logout + .disable() + ); + // @formatter:on + } + + } + static class ReflectingObjectPostProcessor implements ObjectPostProcessor { @Override public O postProcess(O object) { diff --git a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java index 723251e4cf..5313064942 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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,7 +25,10 @@ import org.springframework.security.web.server.context.WebSessionServerSecurityC import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.security.config.Customizer.withDefaults; /** @@ -167,7 +170,8 @@ public class LogoutSpecTests { } @Test - public void logoutWhenDisabledThenPostToLogoutDoesNothing() { + public void logoutWhenDisabledThenDefaultLogoutPageDoesNotExist() { + // @formatter:off SecurityWebFilterChain securityWebFilter = this.http .authorizeExchange() .anyExchange().authenticated() @@ -177,7 +181,7 @@ public class LogoutSpecTests { .build(); WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) + .bindToControllerAndWebFilters(HomeController.class, securityWebFilter) .build(); WebDriver driver = WebTestClientHtmlUnitDriverBuilder @@ -191,15 +195,10 @@ public class LogoutSpecTests { .username("user") .password("password") .submit(FormLoginTests.HomePage.class); - + // @formatter:on homePage.assertAt(); - - FormLoginTests.DefaultLogoutPage.to(driver) - .assertAt() - .logout(); - - homePage - .assertAt(); + FormLoginTests.DefaultLogoutPage.to(driver); + assertThat(driver.getPageSource()).isEmpty(); } @@ -243,4 +242,15 @@ public class LogoutSpecTests { FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class) .assertAt(); } + + @RestController + public static class HomeController { + + @GetMapping("/") + public String ok() { + return "ok"; + } + + } + } From c2473ed9797150df03620e89b30f09f1d0aadcdd Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:05:10 +0200 Subject: [PATCH 270/348] Update to nohttp 0.0.8 Closes gh-9956 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 18cf56e8a0..947087c1d8 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { dependencies { classpath 'io.spring.gradle:spring-build-conventions:0.0.23.1.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" - classpath 'io.spring.nohttp:nohttp-gradle:0.0.6.RELEASE' + classpath 'io.spring.nohttp:nohttp-gradle:0.0.8' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" } repositories { From 766a48c16e725baa76e2d3eba270b869bcd18e00 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:07:18 +0200 Subject: [PATCH 271/348] Update to Reactor Dysprosium-SR20 Closes gh-9957 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 41d25b5ebd..f728dc6c44 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR18' + ext.reactorVersion = 'Dysprosium-SR20' } if (!project.hasProperty('springVersion')) { From 6afe47f164f07417e02da9f1befc5162975afc39 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:08:49 +0200 Subject: [PATCH 272/348] Update to Spring Framework 5.2.15.RELEASE Closes gh-9958 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f728dc6c44..17a5069064 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.13.RELEASE' + ext.springVersion = '5.2.15.RELEASE' } if (!project.hasProperty('springDataVersion')) { From dabe2bacb00252bbd5d8b7a5a621cdf131283830 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:12:22 +0200 Subject: [PATCH 273/348] Update to RSocket 1.0.5 Closes gh-9959 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 17a5069064..20c61aae0e 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -10,7 +10,7 @@ if (!project.hasProperty('springDataVersion')) { ext.springDataVersion = 'Moore-SR13' } -ext.rsocketVersion = '1.0.4' +ext.rsocketVersion = '1.0.5' dependencyManagement { imports { From 613ec13e952fbc356034cc4925ecbc14a8a4a07f Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:12:47 +0200 Subject: [PATCH 274/348] Update to jaxb-impl 2.3.4 Closes gh-9960 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 20c61aae0e..da01f22f62 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -61,7 +61,7 @@ dependencyManagement { dependency 'com.squareup.okhttp3:okhttp:3.14.9' dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' - dependency 'com.sun.xml.bind:jaxb-impl:2.3.3' + dependency 'com.sun.xml.bind:jaxb-impl:2.3.4' dependency 'com.unboundid:unboundid-ldapsdk:4.0.14' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'commons-cli:commons-cli:1.4' From c87c5eb888099917b534be44d8bc0ecd3208d1d9 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:13:54 +0200 Subject: [PATCH 275/348] Update ehcache to 2.10.9.2 Closes gh-9961 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index da01f22f62..f2b3c776c0 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -91,7 +91,7 @@ dependencyManagement { dependency 'net.jcip:jcip-annotations:1.0' dependency 'net.minidev:accessors-smart:1.2' dependency 'net.minidev:json-smart:2.3' - dependency 'net.sf.ehcache:ehcache:2.10.6' + dependency 'net.sf.ehcache:ehcache:2.10.9.2' dependency 'net.sourceforge.htmlunit:htmlunit:2.36.0' dependency 'net.sourceforge.htmlunit:neko-htmlunit:2.34.0' dependency 'net.sourceforge.nekohtml:nekohtml:1.9.22' From 4314c335c8b447b487861a2eb22e2db633c37890 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:15:38 +0200 Subject: [PATCH 276/348] Update to embedded Tomcat websocket 8.5.68 Closes gh-9962 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f2b3c776c0..75c8045526 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -149,7 +149,7 @@ dependencyManagement { dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.45' dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.45' dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.45' - dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.57' + dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.68' dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.45' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" From 3e27f6aecefb2e25d3953041833e891928bdf8e3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:17:18 +0200 Subject: [PATCH 277/348] Update to embedded Apache Tomcat 9.0.48 Closes gh-9963 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 75c8045526..5beb5fe2d3 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.45' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.45' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.45' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.45' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.48' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.48' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.48' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.48' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.68' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.45' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.48' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From c193a06ac5f979cee889b1596a9a24c03d71ef97 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:19:18 +0200 Subject: [PATCH 278/348] Update to Jetty 9.4.42.v20210604 Closes gh-9964 --- gradle/dependency-management.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 5beb5fe2d3..ae033c46a5 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -162,17 +162,17 @@ dependencyManagement { dependency 'org.codehaus.groovy:groovy-json:2.4.21' dependency 'org.codehaus.groovy:groovy:2.4.21' dependency 'org.eclipse.jdt:ecj:3.12.3' - dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.36.v20210114' - dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.36.v20210114' - dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-client:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-http:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-io:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-security:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-server:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-servlet:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-util:9.4.36.v20210114' - dependency 'org.eclipse.jetty:jetty-xml:9.4.36.v20210114' + dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.42.v20210604' + dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.42.v20210604' + dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-client:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-http:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-io:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-security:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-server:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-servlet:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-util:9.4.42.v20210604' + dependency 'org.eclipse.jetty:jetty-xml:9.4.42.v20210604' dependency 'org.eclipse.persistence:javax.persistence:2.2.1' dependency 'org.gebish:geb-ast:0.10.0' dependency 'org.gebish:geb-core:0.10.0' From dc7387075cd7965005534901b3ef78ea2fae2f96 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:21:40 +0200 Subject: [PATCH 279/348] Update to hibernate-entitymanager 5.4.32.Final Closes gh-9965 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ae033c46a5..3567b0301c 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.30.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.32.Final' dependency 'org.hibernate:hibernate-validator:6.1.7.Final' dependency 'org.hsqldb:hsqldb:2.5.1' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From f0d208667bf00a3ae1323a63d345138f5e440480 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:25:05 +0200 Subject: [PATCH 280/348] Update to HSQLDB 2.5.2 Closes gh-9966 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 3567b0301c..946ad72375 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -184,7 +184,7 @@ dependencyManagement { dependency 'org.hibernate:hibernate-core:5.2.18.Final' dependency 'org.hibernate:hibernate-entitymanager:5.4.32.Final' dependency 'org.hibernate:hibernate-validator:6.1.7.Final' - dependency 'org.hsqldb:hsqldb:2.5.1' + dependency 'org.hsqldb:hsqldb:2.5.2' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.javassist:javassist:3.22.0-CR2' dependency 'org.jboss.logging:jboss-logging:3.3.1.Final' From 703f1f1e04175c471b42a7cfd23ee944f731579b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:26:11 +0200 Subject: [PATCH 281/348] Update to org.slf4j 1.7.31 Closes gh-9967 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 946ad72375..344883edfd 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -203,11 +203,11 @@ dependencyManagement { dependency 'org.seleniumhq.selenium:selenium-support:3.141.59' dependency 'org.seleniumhq.selenium:selenium-api:3.141.59' dependency 'org.skyscreamer:jsonassert:1.5.0' - dependency 'org.slf4j:jcl-over-slf4j:1.7.30' - dependency 'org.slf4j:jul-to-slf4j:1.7.30' - dependency 'org.slf4j:log4j-over-slf4j:1.7.30' - dependency 'org.slf4j:slf4j-api:1.7.30' - dependency 'org.slf4j:slf4j-nop:1.7.30' + dependency 'org.slf4j:jcl-over-slf4j:1.7.31' + dependency 'org.slf4j:jul-to-slf4j:1.7.31' + dependency 'org.slf4j:log4j-over-slf4j:1.7.31' + dependency 'org.slf4j:slf4j-api:1.7.31' + dependency 'org.slf4j:slf4j-nop:1.7.31' dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' From 6753e1da68299b4ab6b1fe6a59ccaa5b587db674 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 21 Jun 2021 11:27:47 +0200 Subject: [PATCH 282/348] Update to Spring LDAP Core 2.3.4.RELEASE Closes gh-9968 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 344883edfd..4c06c08170 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -209,7 +209,7 @@ dependencyManagement { dependency 'org.slf4j:slf4j-api:1.7.31' dependency 'org.slf4j:slf4j-nop:1.7.31' dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' - dependency 'org.springframework.ldap:spring-ldap-core:2.3.3.RELEASE' + dependency 'org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' dependency 'org.thymeleaf:thymeleaf-spring5:3.0.12.RELEASE' dependency 'org.unbescape:unbescape:1.1.5.RELEASE' From 560fb35dc56104f6db0a4c60220af1d69afef039 Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Mon, 21 Jun 2021 08:45:10 -0300 Subject: [PATCH 283/348] Relase 5.2.11.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4ae3d6b667..0936619369 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.11.BUILD-SNAPSHOT +version=5.2.11.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From a840aa75312b4165abe4349c26146c9d12264761 Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Mon, 21 Jun 2021 14:45:35 -0300 Subject: [PATCH 284/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0936619369..ec74f92f60 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.6 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.11.RELEASE +version=5.2.12.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 2c1126c5aa374012aa63e563df09df95e636b031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?/usr/local/=CE=95=CE=A8=CE=97=CE=95=CE=9B=CE=A9=CE=9D?= <3042285+djechelon@users.noreply.github.com> Date: Mon, 28 Jun 2021 11:48:07 +0200 Subject: [PATCH 285/348] Improve AuthenticationManagerBeanDefinitionParser XML parsing Closes gh-7282 --- ...enticationManagerBeanDefinitionParser.java | 18 ++++++++----- ...ationManagerBeanDefinitionParserTests.java | 27 ++++++++++++++++++- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParser.java b/config/src/main/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParser.java index 57342c9c81..49593ee13e 100644 --- a/config/src/main/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParser.java +++ b/config/src/main/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -52,6 +52,8 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition private static final String ATT_REF = "ref"; private static final String ATT_ERASE_CREDENTIALS = "erase-credentials"; + private static final String AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME = "defaultAuthenticationEventPublisher"; + public BeanDefinition parse(Element element, ParserContext pc) { String id = element.getAttribute("id"); @@ -124,12 +126,14 @@ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinition false); } - // Add the default event publisher - BeanDefinition publisher = new RootBeanDefinition( - DefaultAuthenticationEventPublisher.class); - String pubId = pc.getReaderContext().generateBeanName(publisher); - pc.registerBeanComponent(new BeanComponentDefinition(publisher, pubId)); - providerManagerBldr.addPropertyReference("authenticationEventPublisher", pubId); + if (!pc.getRegistry().containsBeanDefinition(AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME)) { + // Add the default event publisher to the context + BeanDefinition publisher = new RootBeanDefinition(DefaultAuthenticationEventPublisher.class); + pc.registerBeanComponent(new BeanComponentDefinition(publisher, AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME)); + } + + providerManagerBldr.addPropertyReference("authenticationEventPublisher", + AUTHENTICATION_EVENT_PUBLISHER_BEAN_NAME); pc.registerBeanComponent(new BeanComponentDefinition(providerManagerBldr .getBeanDefinition(), id)); diff --git a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java index 6d64ca25eb..2081f26613 100644 --- a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2021 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. @@ -20,6 +20,7 @@ import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.ProviderManager; @@ -49,6 +50,18 @@ public class AuthenticationManagerBeanDefinitionParserTests { + " " + " " + ""; + + // Issue #7282 + // @formatter:off + private static final String CONTEXT_MULTI = "" + + " " + + " " + + " " + + " " + + " " + + ""; + // @formatter:on + @Rule public final SpringTestRule spring = new SpringTestRule(); @@ -60,6 +73,18 @@ public class AuthenticationManagerBeanDefinitionParserTests { assertThat(context.getBeansOfType(AuthenticationProvider.class)).hasSize(1); } + @Test + public void eventPublishersAreRegisteredAsTopLevelBeans() { + ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext(); + assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1); + } + + @Test + public void onlyOneEventPublisherIsRegisteredForMultipleAuthenticationManagers() { + ConfigurableApplicationContext context = this.spring.context(CONTEXT + '\n' + CONTEXT_MULTI).getContext(); + assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1); + } + @Test public void eventsArePublishedByDefault() throws Exception { ConfigurableApplicationContext appContext = this.spring.context(CONTEXT) From a752919cc91c92a17b49200a86edc61b07134f4f Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 30 Jun 2021 10:27:53 -0500 Subject: [PATCH 286/348] Update to spring-build-conventions:0.0.23.2.RELEASE Closes gh-10029 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 947087c1d8..47ba3e30e1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.23.1.RELEASE' + classpath 'io.spring.gradle:spring-build-conventions:0.0.23.2.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.8' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" From 8bb69c4514eb93b6cb1dcec45f1245146a90ce28 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 30 Jun 2021 10:40:42 -0500 Subject: [PATCH 287/348] Update to use s01.oss.sonatype.org Maven Publishing Closes gh-10024 --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index a7ee4c2be3..0d32c2753c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -156,10 +156,10 @@ try { sh "git clean -dfx" withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) { withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) { - withCredentials([usernamePassword(credentialsId: 'oss-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { + withCredentials([usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhUsername=$OSSRH_USERNAME -PossrhPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_USERNAME -PossrhTokenPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } From e1b6a7ba29465d7618444b98d2b89498a0cdf897 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Tue, 20 Jul 2021 13:09:52 -0500 Subject: [PATCH 288/348] Revert "URL encode client credentials" This reverts commit c0200512a7bd05faf5de5bc504a400b5eb2f998f. Issue gh-9610 gh-9863 Closes gh-10018 --- ...2AuthorizationGrantRequestEntityUtils.java | 23 ++--------- ...eAuthorizationCodeTokenResponseClient.java | 23 ++--------- ...eClientCredentialsTokenResponseClient.java | 26 +++--------- ...ntReactivePasswordTokenResponseClient.java | 26 +++--------- ...activeRefreshTokenTokenResponseClient.java | 26 +++--------- ...tialsGrantRequestEntityConverterTests.java | 40 ------------------- ...ntCredentialsTokenResponseClientTests.java | 36 +---------------- 7 files changed, 23 insertions(+), 177 deletions(-) diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java index 20de672a70..a1ed924307 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/OAuth2AuthorizationGrantRequestEntityUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2018 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. @@ -15,11 +15,6 @@ */ package org.springframework.security.oauth2.client.endpoint; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Collections; - import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -27,6 +22,8 @@ import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import java.util.Collections; + import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; /** @@ -47,23 +44,11 @@ final class OAuth2AuthorizationGrantRequestEntityUtils { HttpHeaders headers = new HttpHeaders(); headers.addAll(DEFAULT_TOKEN_REQUEST_HEADERS); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - String clientId = encodeClientCredential(clientRegistration.getClientId()); - String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); - headers.setBasicAuth(clientId, clientSecret); + headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); } return headers; } - private static String encodeClientCredential(String clientCredential) { - try { - return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - // Will not happen since UTF-8 is a standard charset - throw new IllegalArgumentException(ex); - } - } - private static HttpHeaders getDefaultTokenRequestHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java index 11833b0129..76d49cc367 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveAuthorizationCodeTokenResponseClient.java @@ -15,12 +15,6 @@ */ package org.springframework.security.oauth2.client.endpoint; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; - -import reactor.core.publisher.Mono; - import org.springframework.http.MediaType; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -30,9 +24,10 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.util.Assert; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.util.Assert; +import reactor.core.publisher.Mono; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -79,9 +74,7 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient implements Re .accept(MediaType.APPLICATION_JSON) .headers(headers -> { if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - String clientId = encodeClientCredential(clientRegistration.getClientId()); - String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); - headers.setBasicAuth(clientId, clientSecret); + headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); } }) .body(body) @@ -98,16 +91,6 @@ public class WebClientReactiveAuthorizationCodeTokenResponseClient implements Re }); } - private static String encodeClientCredential(String clientCredential) { - try { - return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - // Will not happen since UTF-8 is a standard charset - throw new IllegalArgumentException(ex); - } - } - private static BodyInserters.FormInserter body(OAuth2AuthorizationExchange authorizationExchange, ClientRegistration clientRegistration) { OAuth2AuthorizationResponse authorizationResponse = authorizationExchange.getAuthorizationResponse(); BodyInserters.FormInserter body = BodyInserters diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java index af7fa64478..6acfd38547 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveClientCredentialsTokenResponseClient.java @@ -15,14 +15,6 @@ */ package org.springframework.security.oauth2.client.endpoint; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Set; -import java.util.function.Consumer; - -import reactor.core.publisher.Mono; - import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -38,6 +30,10 @@ import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.core.publisher.Mono; + +import java.util.Set; +import java.util.function.Consumer; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -102,23 +98,11 @@ public class WebClientReactiveClientCredentialsTokenResponseClient implements Re return headers -> { headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - String clientId = encodeClientCredential(clientRegistration.getClientId()); - String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); - headers.setBasicAuth(clientId, clientSecret); + headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); } }; } - private static String encodeClientCredential(String clientCredential) { - try { - return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - // Will not happen since UTF-8 is a standard charset - throw new IllegalArgumentException(ex); - } - } - private static BodyInserters.FormInserter body(OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) { ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration(); BodyInserters.FormInserter body = BodyInserters diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java index 957c9d5508..41fe121694 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactivePasswordTokenResponseClient.java @@ -15,14 +15,6 @@ */ package org.springframework.security.oauth2.client.endpoint; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.function.Consumer; - -import reactor.core.publisher.Mono; - import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -40,6 +32,10 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.function.Consumer; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -104,23 +100,11 @@ public final class WebClientReactivePasswordTokenResponseClient implements React headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - String clientId = encodeClientCredential(clientRegistration.getClientId()); - String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); - headers.setBasicAuth(clientId, clientSecret); + headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); } }; } - private static String encodeClientCredential(String clientCredential) { - try { - return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - // Will not happen since UTF-8 is a standard charset - throw new IllegalArgumentException(ex); - } - } - private static BodyInserters.FormInserter tokenRequestBody(OAuth2PasswordGrantRequest passwordGrantRequest) { ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration(); BodyInserters.FormInserter body = BodyInserters.fromFormData( diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java index abb200a9c1..6d6daa83d5 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/WebClientReactiveRefreshTokenTokenResponseClient.java @@ -15,14 +15,6 @@ */ package org.springframework.security.oauth2.client.endpoint; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.function.Consumer; - -import reactor.core.publisher.Mono; - import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; @@ -40,6 +32,10 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.Collections; +import java.util.function.Consumer; import static org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse; @@ -92,23 +88,11 @@ public final class WebClientReactiveRefreshTokenTokenResponseClient implements R headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) { - String clientId = encodeClientCredential(clientRegistration.getClientId()); - String clientSecret = encodeClientCredential(clientRegistration.getClientSecret()); - headers.setBasicAuth(clientId, clientSecret); + headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret()); } }; } - private static String encodeClientCredential(String clientCredential) { - try { - return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8.toString()); - } - catch (UnsupportedEncodingException ex) { - // Will not happen since UTF-8 is a standard charset - throw new IllegalArgumentException(ex); - } - } - private static BodyInserters.FormInserter tokenRequestBody(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) { ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration(); BodyInserters.FormInserter body = BodyInserters.fromFormData( diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java index 0f24312af5..28233c9a16 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/endpoint/OAuth2ClientCredentialsGrantRequestEntityConverterTests.java @@ -15,20 +15,13 @@ */ package org.springframework.security.oauth2.client.endpoint; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - import org.junit.Before; import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; @@ -81,37 +74,4 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverterTests { AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("read write"); } - - // gh-9610 - @SuppressWarnings("unchecked") - @Test - public void convertWhenSpecialCharactersThenConvertsWithEncodedClientCredentials() - throws UnsupportedEncodingException { - String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ "; - // @formatter:off - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() - .clientId(clientCredentialWithAnsiKeyboardSpecialCharacters) - .clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters) - .build(); - // @formatter:on - OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest( - clientRegistration); - RequestEntity requestEntity = this.converter.convert(clientCredentialsGrantRequest); - assertThat(requestEntity.getMethod()).isEqualTo(HttpMethod.POST); - assertThat(requestEntity.getUrl().toASCIIString()) - .isEqualTo(clientRegistration.getProviderDetails().getTokenUri()); - HttpHeaders headers = requestEntity.getHeaders(); - assertThat(headers.getAccept()).contains(MediaType.APPLICATION_JSON_UTF8); - assertThat(headers.getContentType()) - .isEqualTo(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8")); - String urlEncodedClientCredential = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters, - StandardCharsets.UTF_8.toString()); - String clientCredentials = Base64.getEncoder().encodeToString( - (urlEncodedClientCredential + ":" + urlEncodedClientCredential).getBytes(StandardCharsets.UTF_8)); - assertThat(headers.getFirst(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials); - MultiValueMap formParameters = (MultiValueMap) requestEntity.getBody(); - assertThat(formParameters.getFirst(OAuth2ParameterNames.GRANT_TYPE)) - .isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()); - assertThat(formParameters.getFirst(OAuth2ParameterNames.SCOPE)).contains(clientRegistration.getScopes()); - } } 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 430efd6927..c4d92d629c 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2019 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. @@ -16,17 +16,12 @@ package org.springframework.security.oauth2.client.endpoint; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; - import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.junit.After; import org.junit.Before; import org.junit.Test; - import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.oauth2.client.registration.ClientRegistration; @@ -87,35 +82,6 @@ public class WebClientReactiveClientCredentialsTokenResponseClientTests { assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser"); } - // gh-9610 - @Test - public void getTokenResponseWhenSpecialCharactersThenSuccessWithEncodedClientCredentials() throws Exception { - // @formatter:off - enqueueJson("{\n" - + " \"access_token\":\"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3\",\n" - + " \"token_type\":\"bearer\",\n" - + " \"expires_in\":3600,\n" - + " \"refresh_token\":\"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk\",\n" - + " \"scope\":\"create\"\n" - + "}"); - // @formatter:on - String clientCredentialWithAnsiKeyboardSpecialCharacters = "~!@#$%^&*()_+{}|:\"<>?`-=[]\\;',./ "; - OAuth2ClientCredentialsGrantRequest request = new OAuth2ClientCredentialsGrantRequest( - this.clientRegistration.clientId(clientCredentialWithAnsiKeyboardSpecialCharacters) - .clientSecret(clientCredentialWithAnsiKeyboardSpecialCharacters).build()); - OAuth2AccessTokenResponse response = this.client.getTokenResponse(request).block(); - RecordedRequest actualRequest = this.server.takeRequest(); - String body = actualRequest.getBody().readUtf8(); - assertThat(response.getAccessToken()).isNotNull(); - String urlEncodedClientCredentialecret = URLEncoder.encode(clientCredentialWithAnsiKeyboardSpecialCharacters, - StandardCharsets.UTF_8.toString()); - String clientCredentials = Base64.getEncoder() - .encodeToString((urlEncodedClientCredentialecret + ":" + urlEncodedClientCredentialecret) - .getBytes(StandardCharsets.UTF_8)); - assertThat(actualRequest.getHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Basic " + clientCredentials); - assertThat(body).isEqualTo("grant_type=client_credentials&scope=read%3Auser"); - } - @Test public void getTokenResponseWhenPostThenSuccess() throws Exception { ClientRegistration registration = this.clientRegistration From 6bcf499884430f24a1ffedd17a1d6b0d40c87efb Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:16:13 +0200 Subject: [PATCH 289/348] Update to org.aspectj 1.9.7 Closes gh-10169 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ec74f92f60..db6d02b5b4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -aspectjVersion=1.9.6 +aspectjVersion=1.9.7 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE version=5.2.12.BUILD-SNAPSHOT From 410c891f75766356c6fd689ca9e1344ba5134315 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:16:43 +0200 Subject: [PATCH 290/348] Update to Reactor Dysprosium-SR22 Closes gh-10163 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 4c06c08170..0912fcf6ab 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR20' + ext.reactorVersion = 'Dysprosium-SR22' } if (!project.hasProperty('springVersion')) { From 7d92b7bda7b44bad59a5d92b3071f6888d0e3c26 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:17:11 +0200 Subject: [PATCH 291/348] Update to Spring Framework 5.2.16.RELEASE Closes gh-10164 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 0912fcf6ab..bb9d7fa429 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.15.RELEASE' + ext.springVersion = '5.2.16.RELEASE' } if (!project.hasProperty('springDataVersion')) { From fb8672a31b64daaed8e62b0cf5b7a9c6c40ee01b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:18:00 +0200 Subject: [PATCH 292/348] Update to jaxb-impl 2.3.5 Closes gh-10165 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index bb9d7fa429..f584d193ad 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -61,7 +61,7 @@ dependencyManagement { dependency 'com.squareup.okhttp3:okhttp:3.14.9' dependency 'com.squareup.okio:okio:1.13.0' dependency 'com.sun.xml.bind:jaxb-core:2.3.0.1' - dependency 'com.sun.xml.bind:jaxb-impl:2.3.4' + dependency 'com.sun.xml.bind:jaxb-impl:2.3.5' dependency 'com.unboundid:unboundid-ldapsdk:4.0.14' dependency 'com.vaadin.external.google:android-json:0.0.20131108.vaadin1' dependency 'commons-cli:commons-cli:1.4' From 7c622dba334473270d0a9d89acfba57ff81e2818 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:18:36 +0200 Subject: [PATCH 293/348] Update to embedded Apache Tomcat 9.0.52 Closes gh-10166 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index f584d193ad..90d3921892 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.48' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.48' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.48' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.48' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.52' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.52' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.52' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.52' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.68' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.48' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.52' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From e4165c4c80826ae5589a4c967743c83fb9ac52f3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:19:03 +0200 Subject: [PATCH 294/348] Update to Jetty 9.4.43.v20210629 Closes gh-10167 --- gradle/dependency-management.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 90d3921892..564a5f5bfc 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -162,17 +162,17 @@ dependencyManagement { dependency 'org.codehaus.groovy:groovy-json:2.4.21' dependency 'org.codehaus.groovy:groovy:2.4.21' dependency 'org.eclipse.jdt:ecj:3.12.3' - dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.42.v20210604' - dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.42.v20210604' - dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-client:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-http:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-io:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-security:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-server:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-servlet:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-util:9.4.42.v20210604' - dependency 'org.eclipse.jetty:jetty-xml:9.4.42.v20210604' + dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.43.v20210629' + dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.43.v20210629' + dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-client:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-http:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-io:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-security:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-server:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-servlet:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-util:9.4.43.v20210629' + dependency 'org.eclipse.jetty:jetty-xml:9.4.43.v20210629' dependency 'org.eclipse.persistence:javax.persistence:2.2.1' dependency 'org.gebish:geb-ast:0.10.0' dependency 'org.gebish:geb-core:0.10.0' From 523cdba8ae87db478a172ed4cc0a99fede799431 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:19:27 +0200 Subject: [PATCH 295/348] Update to org.slf4j 1.7.32 Closes gh-10168 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 564a5f5bfc..b380d385c7 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -203,11 +203,11 @@ dependencyManagement { dependency 'org.seleniumhq.selenium:selenium-support:3.141.59' dependency 'org.seleniumhq.selenium:selenium-api:3.141.59' dependency 'org.skyscreamer:jsonassert:1.5.0' - dependency 'org.slf4j:jcl-over-slf4j:1.7.31' - dependency 'org.slf4j:jul-to-slf4j:1.7.31' - dependency 'org.slf4j:log4j-over-slf4j:1.7.31' - dependency 'org.slf4j:slf4j-api:1.7.31' - dependency 'org.slf4j:slf4j-nop:1.7.31' + dependency 'org.slf4j:jcl-over-slf4j:1.7.32' + dependency 'org.slf4j:jul-to-slf4j:1.7.32' + dependency 'org.slf4j:log4j-over-slf4j:1.7.32' + dependency 'org.slf4j:slf4j-api:1.7.32' + dependency 'org.slf4j:slf4j-nop:1.7.32' dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' From 060523292d2d081d4f48212c1fa998deed3c8c4a Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 12 Aug 2021 16:36:01 +0200 Subject: [PATCH 296/348] Update to embedded Tomcat websocket 8.5.69 Closes gh-10170 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index b380d385c7..b330661712 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -149,7 +149,7 @@ dependencyManagement { dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.52' dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.52' dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.52' - dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.68' + dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.69' dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.52' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" From eaeb419d6562bd1f19d04d8dfca0884c818da919 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 16 Aug 2021 10:47:00 +0200 Subject: [PATCH 297/348] Release 5.2.12.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index db6d02b5b4..47664aae23 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.12.BUILD-SNAPSHOT +version=5.2.12.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 0de2a511843d321d2f6fbfa4ba0c4679395ea972 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 16 Aug 2021 11:10:04 +0200 Subject: [PATCH 298/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 47664aae23..7af4b8ff4c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.12.RELEASE +version=5.2.13.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 9925c6a4c005d453e8ccdb492b8063b77d870196 Mon Sep 17 00:00:00 2001 From: Fabio Guenci Date: Tue, 27 Jul 2021 18:24:11 +0200 Subject: [PATCH 299/348] Preserve Null Claim Values Prior to this commit ClaimTypeConverter returned the claims with the original value for all the claims with a null converted value. The changes allows ClaimTypeConverter to overwrite and return claims with converted value of null. Closes gh-10135 --- .../core/converter/ClaimTypeConverter.java | 2 +- .../jwt/MappedJwtClaimSetConverter.java | 45 +++++++------------ .../jwt/MappedJwtClaimSetConverterTests.java | 12 ++++- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverter.java index 098cb86a01..863f19ad10 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverter.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverter.java index 50a6d382dc..64d2658313 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverter.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -45,19 +45,20 @@ public final class MappedJwtClaimSetConverter implements Converter> claimTypeConverters; - private final Converter, Map> delegate; /** * Constructs a {@link MappedJwtClaimSetConverter} with the provided arguments * * This will completely replace any set of default converters. * + * A converter that returns {@code null} removes the claim from the claim set. A + * converter that returns a non-{@code null} value adds or replaces that claim in the + * claim set. * @param claimTypeConverters The {@link Map} of converters to use */ public MappedJwtClaimSetConverter(Map> claimTypeConverters) { Assert.notNull(claimTypeConverters, "claimTypeConverters cannot be null"); this.claimTypeConverters = claimTypeConverters; - this.delegate = new ClaimTypeConverter(claimTypeConverters); } /** @@ -81,6 +82,9 @@ public final class MappedJwtClaimSetConverter implements Converter convert(Map claims) { Assert.notNull(claims, "claims cannot be null"); - - Map mappedClaims = this.delegate.convert(claims); - - mappedClaims = removeClaims(mappedClaims); - mappedClaims = addClaims(mappedClaims); - + Map mappedClaims = new HashMap<>(claims); + for (Map.Entry> entry : this.claimTypeConverters.entrySet()) { + String claimName = entry.getKey(); + Converter converter = entry.getValue(); + if (converter != null) { + Object claim = claims.get(claimName); + Object mappedClaim = converter.convert(claim); + mappedClaims.compute(claimName, (key, value) -> mappedClaim); + } + } Instant issuedAt = (Instant) mappedClaims.get(JwtClaimNames.IAT); Instant expiresAt = (Instant) mappedClaims.get(JwtClaimNames.EXP); if (issuedAt == null && expiresAt != null) { @@ -159,23 +167,4 @@ public final class MappedJwtClaimSetConverter implements Converter removeClaims(Map claims) { - Map result = new HashMap<>(); - for (Map.Entry entry : claims.entrySet()) { - if (entry.getValue() != null) { - result.put(entry.getKey(), entry.getValue()); - } - } - return result; - } - - private Map addClaims(Map claims) { - Map result = new HashMap<>(claims); - for (Map.Entry> entry : claimTypeConverters.entrySet()) { - if (!claims.containsKey(entry.getKey()) && entry.getValue().convert(null) != null) { - result.put(entry.getKey(), entry.getValue().convert(null)); - } - } - return result; - } } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverterTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverterTests.java index 322890449d..bbe917fabb 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverterTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. @@ -140,11 +140,19 @@ public class MappedJwtClaimSetConverterTests { assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234"); } + // gh-10135 @Test public void convertWhenConverterReturnsNullThenClaimIsRemoved() { MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter - .withDefaults(Collections.emptyMap()); + .withDefaults(Collections.singletonMap(JwtClaimNames.NBF, (nbfClaimValue) -> null)); + Map source = Collections.singletonMap(JwtClaimNames.NBF, Instant.now()); + Map target = converter.convert(source); + assertThat(target).doesNotContainKey(JwtClaimNames.NBF); + } + @Test + public void convertWhenClaimValueIsNullThenClaimIsRemoved() { + MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); Map source = Collections.singletonMap(JwtClaimNames.ISS, null); Map target = converter.convert(source); From b53cab9204b4892af16797592b711ad16da06170 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 22 Sep 2021 16:21:55 -0500 Subject: [PATCH 300/348] Add jenkins user to Jenkinsfile --- Jenkinsfile | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0d32c2753c..bd60ff6dbe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,6 +10,7 @@ currentBuild.result = SUCCESS def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD') +def JENKINS_USER='-Duser.name="spring-builds+jenkins"' try { parallel check: { @@ -20,7 +21,7 @@ try { try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } catch(Exception e) { @@ -43,9 +44,9 @@ try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { if ("master" == env.BRANCH_NAME) { - sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" } else { - sh "./gradlew sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" } } } @@ -65,7 +66,7 @@ try { try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace" } } } catch(Exception e) { @@ -83,7 +84,7 @@ try { try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) { - sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } catch(Exception e) { @@ -101,7 +102,7 @@ try { try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) { - sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } catch(Exception e) { @@ -119,7 +120,7 @@ try { try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) { - sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } catch(Exception e) { @@ -137,7 +138,7 @@ try { try { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) { - sh "./gradlew clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } catch(Exception e) { @@ -159,7 +160,7 @@ try { withCredentials([usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_USERNAME -PossrhTokenPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_USERNAME -PossrhTokenPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } @@ -176,7 +177,7 @@ try { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" } } } @@ -191,7 +192,7 @@ try { withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { withCredentials([ARTIFACTORY_CREDENTIALS]) { withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" + sh "./gradlew $JENKINS_USER deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" } } } From a15eb7eecf0bd0c63af207afefa635a715cd009d Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Wed, 22 Sep 2021 16:23:49 -0500 Subject: [PATCH 301/348] Remove finally block for junit Allow this to be gathered by Gradle enterprise since if build is up to date there will be no tests ran which causes failure. Additionally, Gradle Enterprise displays the tests better than Jenkins. --- Jenkinsfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bd60ff6dbe..28409e463b 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -28,8 +28,6 @@ try { currentBuild.result = 'FAILED: check' throw e - } finally { - junit '**/build/test-results/*/*.xml' } } } From dbe2ef8758ada4cac08214d698340b96df8e6679 Mon Sep 17 00:00:00 2001 From: heowc Date: Thu, 16 Sep 2021 23:26:18 +0900 Subject: [PATCH 302/348] Fix typo Closes gh-10276 --- .../jaas/AbstractJaasAuthenticationProvider.java | 4 ++-- .../authentication/jaas/JaasAuthenticationProvider.java | 2 +- .../crypto/password/DelegatingPasswordEncoderTests.java | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java index 54552a1e0f..42e5de9fd1 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2016 the original author or authors. + * Copyright 2010-2021 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. @@ -287,7 +287,7 @@ public abstract class AbstractJaasAuthenticationProvider * subclasses for different functionality * * @param token The authentication token being processed - * @param ase The excetion that caused the authentication failure + * @param ase The exception that caused the authentication failure */ protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, AuthenticationException ase) { diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java index 34a3f231ee..68fdde7f17 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationProvider.java @@ -247,7 +247,7 @@ public class JaasAuthenticationProvider extends AbstractJaasAuthenticationProvid * subclasses for different functionality * * @param token The authentication token being processed - * @param ase The excetion that caused the authentication failure + * @param ase The exception that caused the authentication failure */ @Override protected void publishFailureEvent(UsernamePasswordAuthenticationToken token, diff --git a/crypto/src/test/java/org/springframework/security/crypto/password/DelegatingPasswordEncoderTests.java b/crypto/src/test/java/org/springframework/security/crypto/password/DelegatingPasswordEncoderTests.java index 8bca684658..439aba4ed8 100644 --- a/crypto/src/test/java/org/springframework/security/crypto/password/DelegatingPasswordEncoderTests.java +++ b/crypto/src/test/java/org/springframework/security/crypto/password/DelegatingPasswordEncoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 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. @@ -135,7 +135,7 @@ public class DelegatingPasswordEncoderTests { } @Test - public void matchesWhenNoClosingPrefixStringThenIllegalArgumentExcetion() { + public void matchesWhenNoClosingPrefixStringThenIllegalArgumentException() { assertThatThrownBy(() -> this.passwordEncoder.matches(this.rawPassword, "{bcrypt" + this.rawPassword)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("There is no PasswordEncoder mapped for the id \"null\""); From f6f351f41990507f465f949fd85e09c9b47d2921 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 27 Sep 2021 14:22:12 -0500 Subject: [PATCH 303/348] Remove Unsupported JDKs The JDKs 9 and 10 are no longer supported by Oracle and the CAs are not up to date, so removing from the build. 18:13:02 * What went wrong: 18:13:02 Execution failed for task ':buildSrc:compileJava'. 18:13:02 > Could not resolve all files for configuration ':buildSrc:compileClasspath'. 18:13:02 > Could not resolve com.thaiopensource:trang:20091111. 18:13:02 Required by: 18:13:02 project :buildSrc 18:13:02 > Could not resolve com.thaiopensource:trang:20091111. 18:13:02 > Could not get resource 'https://repo.maven.apache.org/maven2/com/thaiopensource/trang/20091111/trang-20091111.pom'. 18:13:02 > Could not GET 'https://repo.maven.apache.org/maven2/com/thaiopensource/trang/20091111/trang-20091111.pom'. 18:13:02 > sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target 18:13:02 > Could not resolve net.sourceforge.saxon:saxon:9.1.0.8. 18:13:02 Required by: 18:13:02 project :buildSrc 18:13:02 > Could not resolve net.sourceforge.saxon:saxon:9.1.0.8. 18:13:02 > Could not get resource 'https://repo.maven.apache.org/maven2/net/sourceforge/saxon/saxon/9.1.0.8/saxon-9.1.0.8.pom'. 18:13:02 > Could not GET 'https://repo.maven.apache.org/maven2/net/sourceforge/saxon/saxon/9.1.0.8/saxon-9.1.0.8.pom'. 18:13:02 > sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to request --- Jenkinsfile | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 28409e463b..f18da6ac16 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -74,42 +74,6 @@ try { } } }, - jdk9: { - stage('JDK 9') { - node { - checkout scm - sh "git clean -dfx" - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk9' }"]) { - sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } catch(Exception e) { - currentBuild.result = 'FAILED: jdk9' - throw e - } - } - } - }, - jdk10: { - stage('JDK 10') { - node { - checkout scm - sh "git clean -dfx" - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk10' }"]) { - sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } catch(Exception e) { - currentBuild.result = 'FAILED: jdk10' - throw e - } - } - } - }, jdk11: { stage('JDK 11') { node { From 006fa5ed705e86876575698bc43d2035bc4b94d0 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 14 Oct 2021 10:33:56 +0200 Subject: [PATCH 304/348] Upgrade to embedded Apache Tomcat 9.0.54 Closes gh-10376 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index b330661712..194753b39f 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.52' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.52' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.52' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.52' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.54' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.54' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.54' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.54' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.69' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.52' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.54' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From 27866fb76767819a585b2cb1f6f7b82ce0336c19 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 14 Oct 2021 10:54:23 +0200 Subject: [PATCH 305/348] Update to nohttp 0.0.10 Closes gh-10377 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 47ba3e30e1..5ac8a95273 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { dependencies { classpath 'io.spring.gradle:spring-build-conventions:0.0.23.2.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" - classpath 'io.spring.nohttp:nohttp-gradle:0.0.8' + classpath 'io.spring.nohttp:nohttp-gradle:0.0.10' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" } repositories { From 30fbc269b9927237ae3c6a03131a9602730ad66d Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 14 Oct 2021 10:54:49 +0200 Subject: [PATCH 306/348] Upgrade Reactor to Dysprosium-SR24 Closes gh-10374 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 194753b39f..1b17fb8ce5 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR22' + ext.reactorVersion = 'Dysprosium-SR24' } if (!project.hasProperty('springVersion')) { From 302da19708609d501d43093507fda384719fe7fb Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 14 Oct 2021 10:55:13 +0200 Subject: [PATCH 307/348] Update to embedded Tomcat websocket 8.5.72 Closes gh-10379 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 1b17fb8ce5..c6ba33de74 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -149,7 +149,7 @@ dependencyManagement { dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.54' dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.54' dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.54' - dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.69' + dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.72' dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.54' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" From e74ae71382334355aca63230408b619a3da29b8b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 14 Oct 2021 10:55:36 +0200 Subject: [PATCH 308/348] Update to Jetty 9.4.44.v20210927 Closes gh-10378 --- gradle/dependency-management.gradle | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index c6ba33de74..e6601a26ab 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -162,17 +162,17 @@ dependencyManagement { dependency 'org.codehaus.groovy:groovy-json:2.4.21' dependency 'org.codehaus.groovy:groovy:2.4.21' dependency 'org.eclipse.jdt:ecj:3.12.3' - dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.43.v20210629' - dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.43.v20210629' - dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-client:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-http:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-io:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-security:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-server:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-servlet:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-util:9.4.43.v20210629' - dependency 'org.eclipse.jetty:jetty-xml:9.4.43.v20210629' + dependency 'org.eclipse.jetty.websocket:websocket-api:9.4.44.v20210927' + dependency 'org.eclipse.jetty.websocket:websocket-client:9.4.44.v20210927' + dependency 'org.eclipse.jetty.websocket:websocket-common:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-client:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-http:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-io:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-security:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-server:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-servlet:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-util:9.4.44.v20210927' + dependency 'org.eclipse.jetty:jetty-xml:9.4.44.v20210927' dependency 'org.eclipse.persistence:javax.persistence:2.2.1' dependency 'org.gebish:geb-ast:0.10.0' dependency 'org.gebish:geb-core:0.10.0' From b1588c3d73e809673944ec7053434501702dd124 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 14 Oct 2021 15:07:50 +0200 Subject: [PATCH 309/348] Upgrade Spring Framework to 5.2.18.RELEASE Close gh-10375 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e6601a26ab..d056f87369 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.16.RELEASE' + ext.springVersion = '5.2.18.RELEASE' } if (!project.hasProperty('springDataVersion')) { From 21f0ccd0889f06301a099d170dcfe5624a48ad27 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 12 Oct 2021 12:56:36 -0600 Subject: [PATCH 310/348] Restructure SwitchUserFilter Logs Issue gh-6311 --- .../switchuser/SwitchUserFilter.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java index e7863b90ac..8fa5b9a688 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/switchuser/SwitchUserFilter.java @@ -34,6 +34,7 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.log.LogMessage; import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.AccountStatusUserDetailsChecker; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; @@ -46,6 +47,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; @@ -171,8 +173,10 @@ public class SwitchUserFilter extends GenericFilterBean Authentication targetUser = attemptSwitchUser(request); // update the current context to the new target user - SecurityContextHolder.getContext().setAuthentication(targetUser); - + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(targetUser); + SecurityContextHolder.setContext(context); + this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", targetUser)); // redirect to target url this.successHandler.onAuthenticationSuccess(request, response, targetUser); @@ -189,14 +193,17 @@ public class SwitchUserFilter extends GenericFilterBean Authentication originalUser = attemptExitUser(request); // update the current context back to the original user - SecurityContextHolder.getContext().setAuthentication(originalUser); - + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(originalUser); + SecurityContextHolder.setContext(context); + this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", originalUser)); // redirect to target url this.successHandler.onAuthenticationSuccess(request, response, originalUser); return; } - + this.logger.trace(LogMessage.format("Did not attempt to switch user since request did not match [%s] or [%s]", + this.switchUserMatcher, this.exitUserMatcher)); chain.doFilter(request, response); } @@ -218,25 +225,13 @@ public class SwitchUserFilter extends GenericFilterBean UsernamePasswordAuthenticationToken targetUserRequest; String username = request.getParameter(this.usernameParameter); - - if (username == null) { - username = ""; - } - - if (this.logger.isDebugEnabled()) { - this.logger.debug("Attempt to switch to user [" + username + "]"); - } - + username = (username != null) ? username : ""; + this.logger.debug(LogMessage.format("Attempting to switch to user [%s]", username)); UserDetails targetUser = this.userDetailsService.loadUserByUsername(username); this.userDetailsChecker.check(targetUser); // OK, create the switch user token targetUserRequest = createSwitchUserToken(request, targetUser); - - if (this.logger.isDebugEnabled()) { - this.logger.debug("Switch User Token [" + targetUserRequest + "]"); - } - // publish event if (this.eventPublisher != null) { this.eventPublisher.publishEvent(new AuthenticationSwitchUserEvent( @@ -273,10 +268,9 @@ public class SwitchUserFilter extends GenericFilterBean Authentication original = getSourceAuthentication(current); if (original == null) { - this.logger.debug("Could not find original user Authentication object!"); - throw new AuthenticationCredentialsNotFoundException( - this.messages.getMessage("SwitchUserFilter.noOriginalAuthentication", - "Could not find original Authentication object")); + this.logger.debug("Failed to find original user"); + throw new AuthenticationCredentialsNotFoundException(this.messages + .getMessage("SwitchUserFilter.noOriginalAuthentication", "Failed to find original user")); } // get the source user details @@ -373,8 +367,7 @@ public class SwitchUserFilter extends GenericFilterBean // check for switch user type of authority if (auth instanceof SwitchUserGrantedAuthority) { original = ((SwitchUserGrantedAuthority) auth).getSource(); - this.logger.debug("Found original switch user granted authority [" - + original + "]"); + this.logger.debug(LogMessage.format("Found original switch user granted authority [%s]", original)); } } From 01269e239dd055d740fb4de9a8342f590d53ad3a Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 18 Oct 2021 12:23:34 -0400 Subject: [PATCH 311/348] Release 5.2.13.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7af4b8ff4c..37fc0b77d5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.13.BUILD-SNAPSHOT +version=5.2.13.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From d963a1383290d7b3ddb17ef06ad8c14b49cbf6e4 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Mon, 18 Oct 2021 13:40:40 -0400 Subject: [PATCH 312/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 37fc0b77d5..b2b0c1fff6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.88 springBootVersion=2.2.13.RELEASE -version=5.2.13.RELEASE +version=5.2.14.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 76aff9df625ec09ccdd2e461e76ce71b2d5500bd Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 16 Nov 2021 15:21:05 -0700 Subject: [PATCH 313/348] Fix jwtDecoder Documentation Usage Closes gh-10505 --- .../_includes/servlet/oauth2/oauth2-resourceserver.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc index 080ee953d2..fbbcafd8ff 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc @@ -1422,9 +1422,9 @@ Now that we have a tenant-aware processor and a tenant-aware validator, we can p ---- @Bean JwtDecoder jwtDecoder(JWTProcessor jwtProcessor, OAuth2TokenValidator jwtValidator) { - NimbusJwtDecoder decoder = new NimbusJwtDecoder(processor); + NimbusJwtDecoder decoder = new NimbusJwtDecoder(jwtProcessor); OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<> - (JwtValidators.createDefault(), this.jwtValidator); + (JwtValidators.createDefault(), jwtValidator); decoder.setJwtValidator(validator); return decoder; } From ff7f8f913d480304267970f5659418158afe5d8f Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 16 Nov 2021 15:34:40 -0700 Subject: [PATCH 314/348] Fix setJWTClaimSetJWSKeySelector Typo Closes gh-10504 --- .../_includes/servlet/oauth2/oauth2-resourceserver.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc index fbbcafd8ff..543b99dec2 100644 --- a/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/servlet/oauth2/oauth2-resourceserver.adoc @@ -1375,7 +1375,7 @@ Next, we can construct a `JWTProcessor`: JWTProcessor jwtProcessor(JWTClaimSetJWSKeySelector keySelector) { ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor(); - jwtProcessor.setJWTClaimSetJWSKeySelector(keySelector); + jwtProcessor.setJWTClaimsSetAwareJWSKeySelector(keySelector); return jwtProcessor; } ---- From 01be7eca6eeb44cd9cf6e34d01c77b0d632d51cf Mon Sep 17 00:00:00 2001 From: Marcus Da Coregio Date: Fri, 29 Oct 2021 09:48:24 -0300 Subject: [PATCH 315/348] Improve log message when no CSRF token found Closes gh-10436 --- .../security/web/csrf/MissingCsrfTokenException.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java b/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java index c3fa640ff5..d81e18e6c1 100644 --- a/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java +++ b/web/src/main/java/org/springframework/security/web/csrf/MissingCsrfTokenException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2021 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,6 +25,6 @@ package org.springframework.security.web.csrf; public class MissingCsrfTokenException extends CsrfException { public MissingCsrfTokenException(String actualToken) { - super("Could not verify the provided CSRF token because your session was not found."); + super("Could not verify the provided CSRF token because no token was found to compare."); } } \ No newline at end of file From 1d814f95d5252cadf2981978073193cfacae22c2 Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Wed, 1 Dec 2021 12:36:22 -0600 Subject: [PATCH 316/348] Fix case sensitive headers comparison Closes gh-10557 --- .../header/StaticServerHttpHeadersWriter.java | 17 +++++++++------ .../StaticServerHttpHeadersWriterTests.java | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java index f0e8c7f30e..25dcbddfb2 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java @@ -16,7 +16,6 @@ package org.springframework.security.web.server.header; import java.util.Arrays; -import java.util.Collections; import org.springframework.http.HttpHeaders; import org.springframework.web.server.ServerWebExchange; @@ -42,11 +41,17 @@ public class StaticServerHttpHeadersWriter implements ServerHttpHeadersWriter { @Override public Mono writeHttpHeaders(ServerWebExchange exchange) { HttpHeaders headers = exchange.getResponse().getHeaders(); - boolean containsOneHeaderToAdd = Collections.disjoint(headers.keySet(), this.headersToAdd.keySet()); - if (containsOneHeaderToAdd) { - this.headersToAdd.forEach((name, values) -> { - headers.put(name, values); - }); + // Note: We need to ensure that the following algorithm compares headers + // case insensitively, which should be true of headers.containsKey(). + boolean containsNoHeadersToAdd = true; + for (String headerName : this.headersToAdd.keySet()) { + if (headers.containsKey(headerName)) { + containsNoHeadersToAdd = false; + break; + } + } + if (containsNoHeadersToAdd) { + this.headersToAdd.forEach(headers::put); } return Mono.empty(); } diff --git a/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java index 7f10e88a9c..ab26ac6d62 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java @@ -17,10 +17,13 @@ package org.springframework.security.web.server.header; import static org.assertj.core.api.Assertions.assertThat; +import java.util.Locale; + import org.junit.Test; import org.springframework.http.HttpHeaders; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.server.ServerWebExchange; /** @@ -56,6 +59,24 @@ public class StaticServerHttpHeadersWriterTests { assertThat(headers.get(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS)).containsOnly(headerValue); } + // gh-10557 + @Test + public void writeHeadersWhenHeaderWrittenWithDifferentCaseThenDoesNotWriteHeaders() { + String headerName = HttpHeaders.CACHE_CONTROL.toLowerCase(Locale.ROOT); + String headerValue = "max-age=120"; + this.headers.set(headerName, headerValue); + // Note: This test inverts which collection uses case sensitive headers, + // due to the fact that gh-10557 reports NettyHeadersAdapter as the + // response headers implementation, which is not accessible here. + HttpHeaders caseSensitiveHeaders = new HttpHeaders(new LinkedMultiValueMap<>()); + caseSensitiveHeaders.set(HttpHeaders.CACHE_CONTROL, CacheControlServerHttpHeadersWriter.CACHE_CONTRTOL_VALUE); + caseSensitiveHeaders.set(HttpHeaders.PRAGMA, CacheControlServerHttpHeadersWriter.PRAGMA_VALUE); + caseSensitiveHeaders.set(HttpHeaders.EXPIRES, CacheControlServerHttpHeadersWriter.EXPIRES_VALUE); + this.writer = new StaticServerHttpHeadersWriter(caseSensitiveHeaders); + this.writer.writeHttpHeaders(this.exchange); + assertThat(this.headers.get(headerName)).containsOnly(headerValue); + } + @Test public void writeHeadersWhenMultiHeaderThenWritesAllHeaders() { writer = StaticServerHttpHeadersWriter.builder() From 65b3584ac630ec9f510ff2bfa11d8fd9547e264d Mon Sep 17 00:00:00 2001 From: Steve Riesenberg Date: Wed, 1 Dec 2021 17:12:03 -0600 Subject: [PATCH 317/348] Update copyright year Issue gh-10557 --- .../web/server/header/StaticServerHttpHeadersWriter.java | 2 +- .../web/server/header/StaticServerHttpHeadersWriterTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java b/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java index 25dcbddfb2..d2ee52d0c7 100644 --- a/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java +++ b/web/src/main/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 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. diff --git a/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java b/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java index ab26ac6d62..840643e6bf 100644 --- a/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/header/StaticServerHttpHeadersWriterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2021 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. From 0f177a99647d6b16bf63b3ccb33946133beb4ea3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:32:33 +0200 Subject: [PATCH 318/348] Upgrade Spring Framework to 5.2.19.RELEASE Closes gh-10616 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d056f87369..c688862024 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -3,7 +3,7 @@ if (!project.hasProperty('reactorVersion')) { } if (!project.hasProperty('springVersion')) { - ext.springVersion = '5.2.18.RELEASE' + ext.springVersion = '5.2.19.RELEASE' } if (!project.hasProperty('springDataVersion')) { From f3c28cca7779ec55321d75f27f4dcae64868528f Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:33:19 +0200 Subject: [PATCH 319/348] Upgrade Reactor to Dysprosium-SR25 Closes gh-10617 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index c688862024..be1b76f140 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -1,5 +1,5 @@ if (!project.hasProperty('reactorVersion')) { - ext.reactorVersion = 'Dysprosium-SR24' + ext.reactorVersion = 'Dysprosium-SR25' } if (!project.hasProperty('springVersion')) { From c92dd4637822a5c1bfa7dd45ff23b234c1d7e013 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:35:51 +0200 Subject: [PATCH 320/348] Upgrade to embedded Apache Tomcat 9.0.56 Closes gh-10618 --- gradle/dependency-management.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index be1b76f140..e515e91169 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,12 +145,12 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.54' - dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.54' - dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.54' - dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.54' dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.72' - dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.54' + dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.56' + dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.56' + dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.56' + dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.56' + dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.56' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" From 9c892c1c3530487057ef6a151dc6292a25935886 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:37:39 +0200 Subject: [PATCH 321/348] Update to embedded Tomcat websocket 8.5.73 Closes gh-10619 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index e515e91169..d52e68aeb2 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -145,11 +145,11 @@ dependencyManagement { dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-spec:1.2.5' - dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.72' dependency 'org.apache.tomcat.embed:tomcat-embed-core:9.0.56' dependency 'org.apache.tomcat.embed:tomcat-embed-el:9.0.56' dependency 'org.apache.tomcat.embed:tomcat-embed-jasper:9.0.56' dependency 'org.apache.tomcat.embed:tomcat-embed-logging-log4j:9.0.56' + dependency 'org.apache.tomcat.embed:tomcat-embed-websocket:8.5.73' dependency 'org.apache.tomcat:tomcat-annotations-api:9.0.56' dependency "org.aspectj:aspectjrt:$aspectjVersion" dependency "org.aspectj:aspectjtools:$aspectjVersion" From 0eb7ad597c60715ee45f8776ba838361afb3b438 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:40:53 +0200 Subject: [PATCH 322/348] Update to thymeleaf-spring5 3.0.14 Closes gh-10620 --- gradle/dependency-management.gradle | 2 +- .../helloworld/spring-security-samples-boot-helloworld.gradle | 2 ++ .../boot/insecure/spring-security-samples-boot-insecure.gradle | 2 ++ .../oauth2login/spring-security-samples-boot-oauth2login.gradle | 2 ++ .../spring-security-samples-boot-webflux-form.gradle | 2 ++ 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index d52e68aeb2..9537f2766d 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -211,8 +211,8 @@ dependencyManagement { dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' - dependency 'org.thymeleaf:thymeleaf-spring5:3.0.12.RELEASE' dependency 'org.unbescape:unbescape:1.1.5.RELEASE' + dependency 'org.thymeleaf:thymeleaf-spring5:3.0.14.RELEASE' dependency 'org.w3c.css:sac:1.3' dependency 'xalan:serializer:2.7.2' dependency 'xalan:xalan:2.7.2' diff --git a/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle b/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle index a64426286b..067dd4784c 100644 --- a/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle +++ b/samples/boot/helloworld/spring-security-samples-boot-helloworld.gradle @@ -1,5 +1,7 @@ apply plugin: 'io.spring.convention.spring-sample-boot' +ext['thymeleaf.version']='3.0.14.RELEASE' + dependencies { compile project(':spring-security-config') compile project(':spring-security-web') diff --git a/samples/boot/insecure/spring-security-samples-boot-insecure.gradle b/samples/boot/insecure/spring-security-samples-boot-insecure.gradle index 556ebbb053..dd9f4616ad 100644 --- a/samples/boot/insecure/spring-security-samples-boot-insecure.gradle +++ b/samples/boot/insecure/spring-security-samples-boot-insecure.gradle @@ -1,5 +1,7 @@ apply plugin: 'io.spring.convention.spring-sample-boot' +ext['thymeleaf.version']='3.0.14.RELEASE' + dependencies { compile 'org.springframework.boot:spring-boot-starter-thymeleaf' compile 'org.springframework.boot:spring-boot-starter-web' diff --git a/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle b/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle index ef3a4e27b5..c759769c98 100644 --- a/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle +++ b/samples/boot/oauth2login/spring-security-samples-boot-oauth2login.gradle @@ -1,5 +1,7 @@ apply plugin: 'io.spring.convention.spring-sample-boot' +ext['thymeleaf.version']='3.0.14.RELEASE' + dependencies { compile project(':spring-security-config') compile project(':spring-security-oauth2-client') diff --git a/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle b/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle index 35d9a28149..fea7b21044 100644 --- a/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle +++ b/samples/boot/webflux-form/spring-security-samples-boot-webflux-form.gradle @@ -16,6 +16,8 @@ apply plugin: 'io.spring.convention.spring-sample-boot' +ext['thymeleaf.version']='3.0.14.RELEASE' + dependencies { compile project(':spring-security-core') compile project(':spring-security-config') From e11f90bb33935bc83b411f0dbbbad11350979a9a Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:54:13 +0200 Subject: [PATCH 323/348] Upgrade Unbescape to 1.1.6.RELEASE Closes gh-10621 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 9537f2766d..ba08b0f7c2 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -211,7 +211,7 @@ dependencyManagement { dependency 'org.sonatype.sisu.inject:cglib:2.2.1-v20090111' dependency 'org.springframework.ldap:spring-ldap-core:2.3.4.RELEASE' dependency 'org.synchronoss.cloud:nio-multipart-parser:1.1.0' - dependency 'org.unbescape:unbescape:1.1.5.RELEASE' + dependency 'org.unbescape:unbescape:1.1.6.RELEASE' dependency 'org.thymeleaf:thymeleaf-spring5:3.0.14.RELEASE' dependency 'org.w3c.css:sac:1.3' dependency 'xalan:serializer:2.7.2' From 9d9c8956e384c462c7881589ae6bcd196f3440b4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:54:53 +0200 Subject: [PATCH 324/348] Upgrade attoparser to 2.0.5.RELEASE Closes gh-10625 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index ba08b0f7c2..b8d1a202ff 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -155,7 +155,7 @@ dependencyManagement { dependency "org.aspectj:aspectjtools:$aspectjVersion" dependency "org.aspectj:aspectjweaver:$aspectjVersion" dependency 'org.assertj:assertj-core:3.12.2' - dependency 'org.attoparser:attoparser:2.0.4.RELEASE' + dependency 'org.attoparser:attoparser:2.0.5.RELEASE' dependency 'org.bouncycastle:bcpkix-jdk15on:1.64' dependency 'org.bouncycastle:bcprov-jdk15on:1.64' dependency 'org.codehaus.groovy:groovy-all:2.4.21' From 1739a4f339fa5929539902f2d8d04908d1e21bb8 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:55:18 +0200 Subject: [PATCH 325/348] Upgrade httpcore to 4.4.15 Closes gh-10626 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index b8d1a202ff..a2d940abae 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -139,8 +139,8 @@ dependencyManagement { dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15' dependency 'org.apache.directory.shared:shared-ldap:0.9.15' dependency 'org.apache.httpcomponents:httpclient:4.5.13' - dependency 'org.apache.httpcomponents:httpcore:4.4.8' dependency 'org.apache.httpcomponents:httpmime:4.5.3' + dependency 'org.apache.httpcomponents:httpcore:4.4.15' dependency 'org.apache.mina:mina-core:2.0.0-M6' dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' dependency 'org.apache.taglibs:taglibs-standard-jstlel:1.2.5' From ee4bb21f6a0f18ab7eedd41ada2fdc1427b29f5b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:56:13 +0200 Subject: [PATCH 326/348] Upgrade httpmime to 4.5.13 Closes gh-10627 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index a2d940abae..3d369c3aee 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -139,7 +139,7 @@ dependencyManagement { dependency 'org.apache.directory.shared:shared-ldap-constants:0.9.15' dependency 'org.apache.directory.shared:shared-ldap:0.9.15' dependency 'org.apache.httpcomponents:httpclient:4.5.13' - dependency 'org.apache.httpcomponents:httpmime:4.5.3' + dependency 'org.apache.httpcomponents:httpmime:4.5.13' dependency 'org.apache.httpcomponents:httpcore:4.4.15' dependency 'org.apache.mina:mina-core:2.0.0-M6' dependency 'org.apache.taglibs:taglibs-standard-impl:1.2.5' From c7727891e314dc05e7ca13c7b3f9ffaf149363e4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:56:58 +0200 Subject: [PATCH 327/348] Update to hibernate-entitymanager 5.4.33 Closes gh-10624 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 3d369c3aee..0ab7e1b4b6 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -182,7 +182,7 @@ dependencyManagement { dependency 'org.hibernate.common:hibernate-commons-annotations:5.0.1.Final' dependency 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' dependency 'org.hibernate:hibernate-core:5.2.18.Final' - dependency 'org.hibernate:hibernate-entitymanager:5.4.32.Final' + dependency 'org.hibernate:hibernate-entitymanager:5.4.33' dependency 'org.hibernate:hibernate-validator:6.1.7.Final' dependency 'org.hsqldb:hsqldb:2.5.2' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' From 8922103d128ed6ef9990c371f080f4e7033c9961 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:57:33 +0200 Subject: [PATCH 328/348] Upgrade jboss logging to 3.3.3.Final Closes gh-10623 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 0ab7e1b4b6..da916c9c6e 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -187,7 +187,7 @@ dependencyManagement { dependency 'org.hsqldb:hsqldb:2.5.2' dependency 'org.jasig.cas.client:cas-client-core:3.5.1' dependency 'org.javassist:javassist:3.22.0-CR2' - dependency 'org.jboss.logging:jboss-logging:3.3.1.Final' + dependency 'org.jboss.logging:jboss-logging:3.3.3.Final' dependency 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final' dependency 'org.jboss:jandex:2.0.3.Final' dependency 'org.mockito:mockito-core:3.0.0' From 21c01084aba6d191ac5b7db15669e2c2e0925f85 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 12:57:59 +0200 Subject: [PATCH 329/348] Upgrade jboss jandex to 2.0.5.Final Closes gh-10622 --- gradle/dependency-management.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index da916c9c6e..b8f0c3d9f0 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -189,7 +189,7 @@ dependencyManagement { dependency 'org.javassist:javassist:3.22.0-CR2' dependency 'org.jboss.logging:jboss-logging:3.3.3.Final' dependency 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.0.1.Final' - dependency 'org.jboss:jandex:2.0.3.Final' + dependency 'org.jboss:jandex:2.0.5.Final' dependency 'org.mockito:mockito-core:3.0.0' dependency 'org.objenesis:objenesis:2.6' dependency 'org.openid4java:openid4java-nodeps:0.9.6' From ed03fe81548c6fdfb94a1df996510d05f3c18ae0 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 13:25:41 +0200 Subject: [PATCH 330/348] Update to GAE 1.9.93 Closes gh-10628 --- gradle.properties | 2 +- gradle/dependency-management.gradle | 2 +- samples/xml/gae/spring-security-samples-xml-gae.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index b2b0c1fff6..f8f4c5d5eb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 -gaeVersion=1.9.88 +gaeVersion=1.9.93 springBootVersion=2.2.13.RELEASE version=5.2.14.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index b8f0c3d9f0..6a6b1b0c55 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -48,7 +48,7 @@ dependencyManagement { dependency 'com.fasterxml:classmate:1.3.4' dependency 'com.github.stephenc.jcip:jcip-annotations:1.0-1' dependency 'com.google.appengine:appengine-api-1.0-sdk:$gaeVersion' - dependency 'com.google.appengine:appengine-api-labs:$gaeVersion' + dependency 'com.google.appengine:appengine-api-labs:1.9.88' dependency 'com.google.appengine:appengine-api-stubs:$gaeVersion' dependency 'com.google.appengine:appengine-testing:$gaeVersion' dependency 'com.google.appengine:appengine:$gaeVersion' diff --git a/samples/xml/gae/spring-security-samples-xml-gae.gradle b/samples/xml/gae/spring-security-samples-xml-gae.gradle index 01c5e86f45..36a88ba599 100644 --- a/samples/xml/gae/spring-security-samples-xml-gae.gradle +++ b/samples/xml/gae/spring-security-samples-xml-gae.gradle @@ -38,7 +38,7 @@ dependencies { testCompile "com.google.appengine:appengine-testing:$gaeVersion" - testRuntime "com.google.appengine:appengine-api-labs:$gaeVersion" + testRuntime "com.google.appengine:appengine-api-labs:1.9.88" } appengineRun.onlyIf { !gradle.taskGraph.hasTask(appengineFunctionalTest) } From 3f7041a8d94dfded619bab6154fd26dbeefc496c Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 13:28:54 +0200 Subject: [PATCH 331/348] Release 5.2.14.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f8f4c5d5eb..d80a93c08b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.93 springBootVersion=2.2.13.RELEASE -version=5.2.14.BUILD-SNAPSHOT +version=5.2.14.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From b856e90f8bb334aa8ae100240016e8967fcf01fd Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 16:30:07 +0200 Subject: [PATCH 332/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d80a93c08b..8baa99abef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.93 -springBootVersion=2.2.13.RELEASE +springBootVersion=2.2.15.BUILD-SNAPSHOT version=5.2.14.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 58cba234c085ba7cbdf5e961e2b68453c6932dd3 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 16:31:52 +0200 Subject: [PATCH 333/348] Revert "Next development version" This reverts commit b856e90f8bb334aa8ae100240016e8967fcf01fd. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8baa99abef..d80a93c08b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.93 -springBootVersion=2.2.15.BUILD-SNAPSHOT +springBootVersion=2.2.13.RELEASE version=5.2.14.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 7803089462e030ebf6ca6c10417c1f17a3c9609b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 16:32:20 +0200 Subject: [PATCH 334/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d80a93c08b..53b4126039 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.93 springBootVersion=2.2.13.RELEASE -version=5.2.14.RELEASE +version=5.2.15.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From 0e06882b3f7f78f61eb46367f0f72481f56bd287 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 19:05:57 +0200 Subject: [PATCH 335/348] Update logback to 1.2.9 Closes gh-10642 --- gradle/dependency-management.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/dependency-management.gradle b/gradle/dependency-management.gradle index 6a6b1b0c55..3af4ebb413 100644 --- a/gradle/dependency-management.gradle +++ b/gradle/dependency-management.gradle @@ -40,8 +40,8 @@ dependencyManagement { dependencies { dependency 'antlr:antlr:2.7.7' dependency 'asm:asm:3.1' - dependency 'ch.qos.logback:logback-classic:1.2.3' - dependency 'ch.qos.logback:logback-core:1.2.3' + dependency 'ch.qos.logback:logback-classic:1.2.9' + dependency 'ch.qos.logback:logback-core:1.2.9' dependency 'com.fasterxml.jackson.core:jackson-annotations:2.10.5' dependency 'com.fasterxml.jackson.core:jackson-core:2.10.5' dependency 'com.fasterxml.jackson.core:jackson-databind:2.10.5.1' From 3a3399d388e5426849b60809510f229e6d75b56b Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 19:06:45 +0200 Subject: [PATCH 336/348] Make gretty samples compatible with logback 1.2.9 Explicitly reference the logback.xml file to prevent gretty from configuring the defaults using groovy. Issue gh-10642 --- ...ng-security-samples-javaconfig-concurrency.gradle | 4 ++++ .../spring-security-samples-javaconfig-form.gradle | 4 ++++ ...spring-security-samples-javaconfig-hellojs.gradle | 4 ++++ ...pring-security-samples-javaconfig-hellomvc.gradle | 4 ++++ ...ing-security-samples-javaconfig-helloworld.gradle | 4 ++++ ...pring-security-samples-javaconfig-inmemory.gradle | 4 ++++ .../spring-security-samples-javaconfig-jdbc.gradle | 4 ++++ .../spring-security-samples-javaconfig-ldap.gradle | 4 ++++ ...pring-security-samples-javaconfig-messages.gradle | 4 ++++ .../spring-security-samples-javaconfig-openid.gradle | 4 ++++ ...spring-security-samples-javaconfig-preauth.gradle | 4 ++++ ...ing-security-samples-javaconfig-rememberme.gradle | 4 ++++ ...ng-security-samples-javaconfig-saml2-login.gradle | 4 ++++ .../spring-security-samples-javaconfig-x509.gradle | 4 ++++ .../spring-security-samples-xml-cassample.gradle | 1 + .../xml/cas/cassample/src/main/resources/logback.xml | 12 ++++++++++++ .../spring-security-samples-xml-casserver.gradle | 1 + .../xml/cas/casserver/src/main/resources/logback.xml | 12 ++++++++++++ .../spring-security-samples-xml-contacts.gradle | 4 ++++ .../spring-security-samples-xml-helloworld.gradle | 4 ++++ .../spring-security-samples-xml-insecure.gradle | 4 ++++ samples/xml/insecure/src/main/resources/logback.xml | 12 ++++++++++++ .../spring-security-samples-xml-insecuremvc.gradle | 4 ++++ .../xml/insecuremvc/src/main/resources/logback.xml | 12 ++++++++++++ .../xml/jaas/spring-security-samples-xml-jaas.gradle | 4 ++++ .../xml/ldap/spring-security-samples-xml-ldap.gradle | 4 ++++ .../openid/spring-security-samples-xml-openid.gradle | 4 ++++ .../spring-security-samples-xml-preauth.gradle | 4 ++++ .../spring-security-samples-xml-servletapi.gradle | 4 ++++ .../spring-security-samples-xml-tutorial.gradle | 4 ++++ 30 files changed, 146 insertions(+) create mode 100644 samples/xml/cas/cassample/src/main/resources/logback.xml create mode 100644 samples/xml/cas/casserver/src/main/resources/logback.xml create mode 100644 samples/xml/insecure/src/main/resources/logback.xml create mode 100644 samples/xml/insecuremvc/src/main/resources/logback.xml diff --git a/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle b/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle index 7a8e417d8e..db52c5f523 100644 --- a/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle +++ b/samples/javaconfig/concurrency/spring-security-samples-javaconfig-concurrency.gradle @@ -18,3 +18,7 @@ dependencies { runtime 'ch.qos.logback:logback-classic' runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle b/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle index 3dee895966..febffd29d4 100644 --- a/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle +++ b/samples/javaconfig/form/spring-security-samples-javaconfig-form.gradle @@ -38,3 +38,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle b/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle index 86700d410d..2b8fb620c6 100644 --- a/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle +++ b/samples/javaconfig/hellojs/spring-security-samples-javaconfig-hellojs.gradle @@ -18,3 +18,7 @@ dependencies { runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/hellomvc/spring-security-samples-javaconfig-hellomvc.gradle b/samples/javaconfig/hellomvc/spring-security-samples-javaconfig-hellomvc.gradle index 3074d45935..aa58727557 100644 --- a/samples/javaconfig/hellomvc/spring-security-samples-javaconfig-hellomvc.gradle +++ b/samples/javaconfig/hellomvc/spring-security-samples-javaconfig-hellomvc.gradle @@ -20,3 +20,7 @@ dependencies { testCompile project(':spring-security-test') } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/helloworld/spring-security-samples-javaconfig-helloworld.gradle b/samples/javaconfig/helloworld/spring-security-samples-javaconfig-helloworld.gradle index 6d3e843624..cbb48aac21 100644 --- a/samples/javaconfig/helloworld/spring-security-samples-javaconfig-helloworld.gradle +++ b/samples/javaconfig/helloworld/spring-security-samples-javaconfig-helloworld.gradle @@ -27,3 +27,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/inmemory/spring-security-samples-javaconfig-inmemory.gradle b/samples/javaconfig/inmemory/spring-security-samples-javaconfig-inmemory.gradle index 3074d45935..aa58727557 100644 --- a/samples/javaconfig/inmemory/spring-security-samples-javaconfig-inmemory.gradle +++ b/samples/javaconfig/inmemory/spring-security-samples-javaconfig-inmemory.gradle @@ -20,3 +20,7 @@ dependencies { testCompile project(':spring-security-test') } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/jdbc/spring-security-samples-javaconfig-jdbc.gradle b/samples/javaconfig/jdbc/spring-security-samples-javaconfig-jdbc.gradle index e3ff16ce6a..caf884deef 100644 --- a/samples/javaconfig/jdbc/spring-security-samples-javaconfig-jdbc.gradle +++ b/samples/javaconfig/jdbc/spring-security-samples-javaconfig-jdbc.gradle @@ -39,3 +39,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/ldap/spring-security-samples-javaconfig-ldap.gradle b/samples/javaconfig/ldap/spring-security-samples-javaconfig-ldap.gradle index 1fc70cb879..211b940198 100644 --- a/samples/javaconfig/ldap/spring-security-samples-javaconfig-ldap.gradle +++ b/samples/javaconfig/ldap/spring-security-samples-javaconfig-ldap.gradle @@ -37,3 +37,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/messages/spring-security-samples-javaconfig-messages.gradle b/samples/javaconfig/messages/spring-security-samples-javaconfig-messages.gradle index 43c2f49ac2..312b956a65 100644 --- a/samples/javaconfig/messages/spring-security-samples-javaconfig-messages.gradle +++ b/samples/javaconfig/messages/spring-security-samples-javaconfig-messages.gradle @@ -29,3 +29,7 @@ dependencies { providedCompile 'javax.servlet:javax.servlet-api' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/openid/spring-security-samples-javaconfig-openid.gradle b/samples/javaconfig/openid/spring-security-samples-javaconfig-openid.gradle index a62026e8db..3d74785924 100644 --- a/samples/javaconfig/openid/spring-security-samples-javaconfig-openid.gradle +++ b/samples/javaconfig/openid/spring-security-samples-javaconfig-openid.gradle @@ -20,3 +20,7 @@ dependencies { runtime 'net.sourceforge.nekohtml:nekohtml' runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/preauth/spring-security-samples-javaconfig-preauth.gradle b/samples/javaconfig/preauth/spring-security-samples-javaconfig-preauth.gradle index 646b6878d0..4734b4db8b 100644 --- a/samples/javaconfig/preauth/spring-security-samples-javaconfig-preauth.gradle +++ b/samples/javaconfig/preauth/spring-security-samples-javaconfig-preauth.gradle @@ -17,3 +17,7 @@ dependencies { runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/rememberme/spring-security-samples-javaconfig-rememberme.gradle b/samples/javaconfig/rememberme/spring-security-samples-javaconfig-rememberme.gradle index 4d52c8ec1e..3d2295fbc8 100644 --- a/samples/javaconfig/rememberme/spring-security-samples-javaconfig-rememberme.gradle +++ b/samples/javaconfig/rememberme/spring-security-samples-javaconfig-rememberme.gradle @@ -17,3 +17,7 @@ dependencies { runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle b/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle index 3ca4ac602d..73015d825f 100644 --- a/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle +++ b/samples/javaconfig/saml2login/spring-security-samples-javaconfig-saml2-login.gradle @@ -9,3 +9,7 @@ dependencies { testCompile project(':spring-security-test') } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/javaconfig/x509/spring-security-samples-javaconfig-x509.gradle b/samples/javaconfig/x509/spring-security-samples-javaconfig-x509.gradle index 7150dd3291..e00961574a 100644 --- a/samples/javaconfig/x509/spring-security-samples-javaconfig-x509.gradle +++ b/samples/javaconfig/x509/spring-security-samples-javaconfig-x509.gradle @@ -17,3 +17,7 @@ dependencies { runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/cas/cassample/spring-security-samples-xml-cassample.gradle b/samples/xml/cas/cassample/spring-security-samples-xml-cassample.gradle index ba67be2f28..ad41a9e614 100644 --- a/samples/xml/cas/cassample/spring-security-samples-xml-cassample.gradle +++ b/samples/xml/cas/cassample/spring-security-samples-xml-cassample.gradle @@ -65,6 +65,7 @@ gretty { sslKeyStorePath = keystore sslKeyStorePassword = password jvmArgs = ["-Djavax.net.ssl.trustStore=${keystore}", "-Djavax.net.ssl.trustStorePassword=${password}"] + logbackConfigFile = "src/main/resources/logback.xml" } def casServer() { diff --git a/samples/xml/cas/cassample/src/main/resources/logback.xml b/samples/xml/cas/cassample/src/main/resources/logback.xml new file mode 100644 index 0000000000..3ebbcc0ddd --- /dev/null +++ b/samples/xml/cas/cassample/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/samples/xml/cas/casserver/spring-security-samples-xml-casserver.gradle b/samples/xml/cas/casserver/spring-security-samples-xml-casserver.gradle index 3e3498021f..fb92d46b02 100644 --- a/samples/xml/cas/casserver/spring-security-samples-xml-casserver.gradle +++ b/samples/xml/cas/casserver/spring-security-samples-xml-casserver.gradle @@ -53,4 +53,5 @@ gretty { sslKeyStorePath = keystore sslKeyStorePassword = password jvmArgs = ["-Djavax.net.ssl.trustStore=${keystore}", "-Djavax.net.ssl.trustStorePassword=${password}"] + logbackConfigFile = "src/main/resources/logback.xml" } diff --git a/samples/xml/cas/casserver/src/main/resources/logback.xml b/samples/xml/cas/casserver/src/main/resources/logback.xml new file mode 100644 index 0000000000..3ebbcc0ddd --- /dev/null +++ b/samples/xml/cas/casserver/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/samples/xml/contacts/spring-security-samples-xml-contacts.gradle b/samples/xml/contacts/spring-security-samples-xml-contacts.gradle index 46f2eccde5..8809838ff3 100644 --- a/samples/xml/contacts/spring-security-samples-xml-contacts.gradle +++ b/samples/xml/contacts/spring-security-samples-xml-contacts.gradle @@ -41,3 +41,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/helloworld/spring-security-samples-xml-helloworld.gradle b/samples/xml/helloworld/spring-security-samples-xml-helloworld.gradle index 5bd5b1fd09..4b132a6720 100644 --- a/samples/xml/helloworld/spring-security-samples-xml-helloworld.gradle +++ b/samples/xml/helloworld/spring-security-samples-xml-helloworld.gradle @@ -31,3 +31,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/insecure/spring-security-samples-xml-insecure.gradle b/samples/xml/insecure/spring-security-samples-xml-insecure.gradle index 26fc7ac5b4..c0653ec6e9 100644 --- a/samples/xml/insecure/spring-security-samples-xml-insecure.gradle +++ b/samples/xml/insecure/spring-security-samples-xml-insecure.gradle @@ -26,3 +26,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/insecure/src/main/resources/logback.xml b/samples/xml/insecure/src/main/resources/logback.xml new file mode 100644 index 0000000000..3ebbcc0ddd --- /dev/null +++ b/samples/xml/insecure/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/samples/xml/insecuremvc/spring-security-samples-xml-insecuremvc.gradle b/samples/xml/insecuremvc/spring-security-samples-xml-insecuremvc.gradle index 5a5a2de952..426108950b 100644 --- a/samples/xml/insecuremvc/spring-security-samples-xml-insecuremvc.gradle +++ b/samples/xml/insecuremvc/spring-security-samples-xml-insecuremvc.gradle @@ -15,3 +15,7 @@ dependencies { runtime 'opensymphony:sitemesh' } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/insecuremvc/src/main/resources/logback.xml b/samples/xml/insecuremvc/src/main/resources/logback.xml new file mode 100644 index 0000000000..3ebbcc0ddd --- /dev/null +++ b/samples/xml/insecuremvc/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/samples/xml/jaas/spring-security-samples-xml-jaas.gradle b/samples/xml/jaas/spring-security-samples-xml-jaas.gradle index a986704ef2..530bd98b72 100644 --- a/samples/xml/jaas/spring-security-samples-xml-jaas.gradle +++ b/samples/xml/jaas/spring-security-samples-xml-jaas.gradle @@ -31,3 +31,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/ldap/spring-security-samples-xml-ldap.gradle b/samples/xml/ldap/spring-security-samples-xml-ldap.gradle index 6e7b2a6cce..056bc03313 100644 --- a/samples/xml/ldap/spring-security-samples-xml-ldap.gradle +++ b/samples/xml/ldap/spring-security-samples-xml-ldap.gradle @@ -29,3 +29,7 @@ dependencies { integrationTestCompile seleniumDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/openid/spring-security-samples-xml-openid.gradle b/samples/xml/openid/spring-security-samples-xml-openid.gradle index f6af367680..ed38d54720 100644 --- a/samples/xml/openid/spring-security-samples-xml-openid.gradle +++ b/samples/xml/openid/spring-security-samples-xml-openid.gradle @@ -12,3 +12,7 @@ dependencies { runtime jstlDependencies runtime slf4jDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/preauth/spring-security-samples-xml-preauth.gradle b/samples/xml/preauth/spring-security-samples-xml-preauth.gradle index 6645d9e605..5788d9c4cf 100644 --- a/samples/xml/preauth/spring-security-samples-xml-preauth.gradle +++ b/samples/xml/preauth/spring-security-samples-xml-preauth.gradle @@ -14,3 +14,7 @@ dependencies { //jettyRun { // userRealms = [jettyRun.class.classLoader.loadClass('org.mortbay.jetty.security.HashUserRealm').newInstance('Preauth Realm' '$projectDir/realm.properties')] //} + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/servletapi/spring-security-samples-xml-servletapi.gradle b/samples/xml/servletapi/spring-security-samples-xml-servletapi.gradle index e3a9e76e1d..b3cf9d65ad 100644 --- a/samples/xml/servletapi/spring-security-samples-xml-servletapi.gradle +++ b/samples/xml/servletapi/spring-security-samples-xml-servletapi.gradle @@ -19,3 +19,7 @@ dependencies { } eclipse.wtp.component.contextPath = 'servletapi' + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} diff --git a/samples/xml/tutorial/spring-security-samples-xml-tutorial.gradle b/samples/xml/tutorial/spring-security-samples-xml-tutorial.gradle index 8c520fdb2b..9bd1caf968 100644 --- a/samples/xml/tutorial/spring-security-samples-xml-tutorial.gradle +++ b/samples/xml/tutorial/spring-security-samples-xml-tutorial.gradle @@ -14,3 +14,7 @@ dependencies { runtime project(':spring-security-web') runtime jstlDependencies } + +gretty { + logbackConfigFile = "src/main/resources/logback.xml" +} From 60e1db16ab32e58932c9e4265f7fccf4136a3b1a Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 20:40:02 +0200 Subject: [PATCH 337/348] Release 5.2.15.RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 53b4126039..c7d244f794 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.93 springBootVersion=2.2.13.RELEASE -version=5.2.15.BUILD-SNAPSHOT +version=5.2.15.RELEASE org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From e609808d1d4336bfdb687e6334cf890a84aa54f6 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 20 Dec 2021 21:18:24 +0200 Subject: [PATCH 338/348] Next development version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c7d244f794..6fe0a8a13f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ aspectjVersion=1.9.7 gaeVersion=1.9.93 springBootVersion=2.2.13.RELEASE -version=5.2.15.RELEASE +version=5.2.16.BUILD-SNAPSHOT org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError From ff5ccf7c7bd056dd3f3476fab540a46315f1f8ca Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 3 Jan 2022 12:54:28 +0200 Subject: [PATCH 339/348] Remove from CI --- Jenkinsfile | 191 ---------------------------------------------------- 1 file changed, 191 deletions(-) delete mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index f18da6ac16..0000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,191 +0,0 @@ -def projectProperties = [ - [$class: 'BuildDiscarderProperty', - strategy: [$class: 'LogRotator', numToKeepStr: '5']], - pipelineTriggers([cron('@daily')]) -] -properties(projectProperties) - -def SUCCESS = hudson.model.Result.SUCCESS.toString() -currentBuild.result = SUCCESS - - -def ARTIFACTORY_CREDENTIALS = usernamePassword(credentialsId: '02bd1690-b54f-4c9f-819d-a77cb7a9822c', usernameVariable: 'ARTIFACTORY_USERNAME', passwordVariable: 'ARTIFACTORY_PASSWORD') -def JENKINS_USER='-Duser.name="spring-builds+jenkins"' - -try { - parallel check: { - stage('Check') { - node { - checkout scm - sh "git clean -dfx" - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew $JENKINS_USER clean check -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } catch(Exception e) { - - currentBuild.result = 'FAILED: check' - throw e - } - } - } - }, - sonar: { - stage('Sonar') { - node { - checkout scm - sh "git clean -dfx" - withCredentials([string(credentialsId: 'spring-sonar.login', variable: 'SONAR_LOGIN')]) { - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - if ("master" == env.BRANCH_NAME) { - sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" - } else { - sh "./gradlew $JENKINS_USER sonarqube -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PexcludeProjects='**/samples/**' -Dsonar.projectKey='spring-security-${env.BRANCH_NAME}' -Dsonar.projectName='spring-security-${env.BRANCH_NAME}' -Dsonar.host.url=$SPRING_SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN --refresh-dependencies --no-daemon --stacktrace" - } - } - } - } catch(Exception e) { - currentBuild.result = 'FAILED: sonar' - throw e - } - } - } - } - }, - snapshots: { - stage('Snapshot Tests') { - node { - checkout scm - sh "git clean -dfx" - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Lovelace-BUILD-SNAPSHOT --refresh-dependencies --no-daemon --stacktrace" - } - } - } catch(Exception e) { - currentBuild.result = 'FAILED: snapshots' - throw e - } - } - } - }, - jdk11: { - stage('JDK 11') { - node { - checkout scm - sh "git clean -dfx" - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk11' }"]) { - sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } catch(Exception e) { - currentBuild.result = 'FAILED: jdk11' - throw e - } - } - } - }, - jdk12: { - stage('JDK 12') { - node { - checkout scm - sh "git clean -dfx" - try { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'openjdk12' }"]) { - sh "./gradlew $JENKINS_USER clean test -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } catch(Exception e) { - currentBuild.result = 'FAILED: jdk12' - throw e - } - } - } - } - - if(currentBuild.result == 'SUCCESS') { - parallel artifacts: { - stage('Deploy Artifacts') { - node { - checkout scm - sh "git clean -dfx" - withCredentials([file(credentialsId: 'spring-signing-secring.gpg', variable: 'SIGNING_KEYRING_FILE')]) { - withCredentials([string(credentialsId: 'spring-gpg-passphrase', variable: 'SIGNING_PASSWORD')]) { - withCredentials([usernamePassword(credentialsId: 'oss-s01-token', passwordVariable: 'OSSRH_PASSWORD', usernameVariable: 'OSSRH_USERNAME')]) { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew $JENKINS_USER deployArtifacts finalizeDeployArtifacts -Psigning.secretKeyRingFile=$SIGNING_KEYRING_FILE -Psigning.keyId=$SPRING_SIGNING_KEYID -Psigning.password='$SIGNING_PASSWORD' -PossrhTokenUsername=$OSSRH_USERNAME -PossrhTokenPassword=$OSSRH_PASSWORD -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } - } - } - } - } - }, - docs: { - stage('Deploy Docs') { - node { - checkout scm - sh "git clean -dfx" - withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew $JENKINS_USER deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME -PartifactoryUsername=$ARTIFACTORY_USERNAME -PartifactoryPassword=$ARTIFACTORY_PASSWORD --refresh-dependencies --no-daemon --stacktrace" - } - } - } - } - } - }, - schema: { - stage('Deploy Schema') { - node { - checkout scm - sh "git clean -dfx" - withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) { - withCredentials([ARTIFACTORY_CREDENTIALS]) { - withEnv(["JAVA_HOME=${ tool 'jdk8' }"]) { - sh "./gradlew $JENKINS_USER deploySchema -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace" - } - } - } - } - } - } - } -} catch(Exception e) { - currentBuild.result = 'FAILED: deploys' - throw e -} finally { - def buildStatus = currentBuild.result - def buildNotSuccess = !SUCCESS.equals(buildStatus) - def lastBuildNotSuccess = !SUCCESS.equals(currentBuild.previousBuild?.result) - - if(buildNotSuccess || lastBuildNotSuccess) { - - stage('Notifiy') { - node { - final def RECIPIENTS = [[$class: 'DevelopersRecipientProvider'], [$class: 'RequesterRecipientProvider']] - - def subject = "${buildStatus}: Build ${env.JOB_NAME} ${env.BUILD_NUMBER} status is now ${buildStatus}" - def details = """The build status changed to ${buildStatus}. For details see ${env.BUILD_URL}""" - - emailext ( - subject: subject, - body: details, - recipientProviders: RECIPIENTS, - to: "$SPRING_SECURITY_TEAM_EMAILS" - ) - } - } - } -} From f8d5a44c309c05055f75c1f14f20fa922d2ffba4 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 3 Jan 2022 12:55:16 +0200 Subject: [PATCH 340/348] Add End-of-Life Notice --- README.adoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.adoc b/README.adoc index 73e398e181..4aa787375f 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,11 @@ image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge] +[NOTE] +====== +This branch of Spring Security has reached its https://github.com/spring-projects/spring-framework/wiki/Spring-Framework-Versions#supported-versions[End of Life], meaning that there are no further maintenance releases or security patches planned. +Please migrate to a supported branch as soon as possible. +====== + = Spring Security Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 5.0 requires Spring 5.0 as From f9f5b4345f91ec39814bdc614e9fab84a3abbaad Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Thu, 7 Apr 2022 16:32:18 +0200 Subject: [PATCH 341/348] Re-enable CI for 5.2.x Closes gh-11075 --- .../continuous-integration-workflow.yml | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 .github/workflows/continuous-integration-workflow.yml diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml new file mode 100644 index 0000000000..0f606c2f5f --- /dev/null +++ b/.github/workflows/continuous-integration-workflow.yml @@ -0,0 +1,192 @@ +name: CI + +on: + push: + schedule: + - cron: '0 10 * * *' # Once per day at 10am UTC + workflow_dispatch: # Manual trigger + +env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} + COMMIT_OWNER: ${{ github.event.pusher.name }} + COMMIT_SHA: ${{ github.sha }} + ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }} + +jobs: + prerequisites: + name: Pre-requisites for building + runs-on: ubuntu-latest + outputs: + runjobs: ${{ steps.continue.outputs.runjobs }} + steps: + - id: continue + name: Determine if should continue + if: env.RUN_JOBS == 'true' + run: echo "::set-output name=runjobs::true" + build_jdk_8: + name: Build JDK 8 + needs: [prerequisites] + runs-on: ubuntu-latest + if: needs.prerequisites.outputs.runjobs + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v1 + with: + java-version: '8' + - name: Setup gradle user name + run: | + mkdir -p ~/.gradle + echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + - name: Build with Gradle + env: + GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} + run: ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" + test_jdk_11: + name: Test JDK 11 + needs: [prerequisites] + runs-on: ubuntu-latest + if: needs.prerequisites.outputs.runjobs + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: '11' + - name: Setup gradle user name + run: | + mkdir -p ~/.gradle + echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} + - name: Build with Gradle + env: + GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} + GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} + run: ./gradlew clean test --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" + snapshot_tests: + name: Test against snapshots + needs: [prerequisites] + runs-on: ubuntu-latest + if: needs.prerequisites.outputs.runjobs + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '11' + - name: Setup gradle user name + run: | + mkdir -p ~/.gradle + echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties + - name: Snapshot Tests + run: | + export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" + export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" + export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" + ./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PspringVersion='5.2.+' -PreactorVersion=Dysprosium-BUILD-SNAPSHOT -PspringDataVersion=Moore-BUILD-SNAPSHOT -PlocksDisabled --stacktrace + deploy_artifacts: + name: Deploy Artifacts + needs: [test_jdk_11, build_jdk_8, snapshot_tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '11' + - name: Setup gradle user name + run: | + mkdir -p ~/.gradle + echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties + - name: Deploy artifacts + run: | + export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" + export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" + export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" + ./gradlew deployArtifacts -PossrhTokenUsername="$OSSRH_TOKEN_USERNAME" -PossrhTokenPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel + ./gradlew finalizeDeployArtifacts -PossrhTokenUsername="$OSSRH_TOKEN_USERNAME" -PossrhTokenPassword="$OSSRH_TOKEN_PASSWORD" -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" --stacktrace --no-parallel + env: + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }} + OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} + OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} + ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} + ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} + deploy_docs: + name: Deploy Docs + needs: [test_jdk_11, build_jdk_8, snapshot_tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '11' + - name: Setup gradle user name + run: | + mkdir -p ~/.gradle + echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties + - name: Deploy Docs + run: | + export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" + export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" + export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" + ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace + env: + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} + DOCS_HOST: ${{ secrets.DOCS_HOST }} + deploy_schema: + name: Deploy Schema + needs: [test_jdk_11, build_jdk_8, snapshot_tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v1 + with: + java-version: '11' + - name: Setup gradle user name + run: | + mkdir -p ~/.gradle + echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties + - name: Deploy Schema + run: | + export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" + export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" + export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" + ./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace --info + env: + DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} + DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} + DOCS_HOST: ${{ secrets.DOCS_HOST }} + notify_result: + name: Check for failures + needs: [test_jdk_11, build_jdk_8, snapshot_tests, deploy_artifacts, deploy_docs, deploy_schema] + if: failure() + runs-on: ubuntu-latest + steps: + - name: Send Slack message + uses: Gamesight/slack-workflow-status@v1.0.1 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} + channel: '#spring-security-ci' + name: 'CI Notifier' From 764ca38596ebe138139f2b4def124dad2b4909ec Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Fri, 8 Apr 2022 11:30:07 +0200 Subject: [PATCH 342/348] Use explicit docs host Issue gh-11075 --- .github/workflows/continuous-integration-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index 0f606c2f5f..a3bc8b913f 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -148,7 +148,7 @@ jobs: export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" - ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace + ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost=docs-ip.spring.io --stacktrace env: DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} @@ -172,7 +172,7 @@ jobs: export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" - ./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost="$DOCS_HOST" --stacktrace --info + ./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost=docs-ip.spring.io --stacktrace --info env: DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} From cb2291cc245a6b68d02ff2c4c6666bf93e7b72b8 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Mon, 11 Apr 2022 18:08:06 +0200 Subject: [PATCH 343/348] Update to spring-build-conventions:0.0.23.3.RELEASE Closes gh-11093 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5ac8a95273..99aadfceca 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.23.2.RELEASE' + classpath 'io.spring.gradle:spring-build-conventions:0.0.23.3.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.10' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" From e75382736d63d11d16c82b09f48ba1674f0cf503 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 12 Apr 2022 12:35:04 +0200 Subject: [PATCH 344/348] Update to spring-build-conventions:0.0.23.4.RELEASE Closes gh-11098 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 99aadfceca..978deff2aa 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { dependencies { - classpath 'io.spring.gradle:spring-build-conventions:0.0.23.3.RELEASE' + classpath 'io.spring.gradle:spring-build-conventions:0.0.23.4.RELEASE' classpath "org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion" classpath 'io.spring.nohttp:nohttp-gradle:0.0.10' classpath "io.freefair.gradle:aspectj-plugin:4.0.2" From 1f01203462bb3af3c96b902324b0f0d5eb51f438 Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 12 Apr 2022 13:01:44 +0200 Subject: [PATCH 345/348] Remove unused host parameter in CI Issue gh-11075 --- .github/workflows/continuous-integration-workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml index a3bc8b913f..dd3c03c76d 100644 --- a/.github/workflows/continuous-integration-workflow.yml +++ b/.github/workflows/continuous-integration-workflow.yml @@ -148,7 +148,7 @@ jobs: export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" - ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost=docs-ip.spring.io --stacktrace + ./gradlew deployDocs -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" --stacktrace env: DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} @@ -172,7 +172,7 @@ jobs: export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER" export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD" export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY" - ./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" -PdeployDocsHost=docs-ip.spring.io --stacktrace --info + ./gradlew deploySchema -PdeployDocsSshKey="$DOCS_SSH_KEY" -PdeployDocsSshUsername="$DOCS_USERNAME" --stacktrace --info env: DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }} DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }} From 0ece0e601287f8f0bdaae3ce53bd3567aecf8d94 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 16 May 2022 09:46:33 -0500 Subject: [PATCH 346/348] Extract rejectNonPrintableAsciiCharactersInFieldName Closes gh-11234 --- .../web/firewall/StrictHttpFirewall.java | 15 ++++++++++ .../web/firewall/StrictHttpFirewallTests.java | 29 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java b/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java index 25dfe2b3a1..aef2a7b533 100644 --- a/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java +++ b/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java @@ -344,6 +344,11 @@ public class StrictHttpFirewall implements HttpFirewall { if (!containsOnlyPrintableAsciiCharacters(requestUri)) { throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters."); } + rejectNonPrintableAsciiCharactersInFieldName(request.getRequestURI(), "requestURI"); + rejectNonPrintableAsciiCharactersInFieldName(request.getServletPath(), "servletPath"); + rejectNonPrintableAsciiCharactersInFieldName(request.getPathInfo(), "pathInfo"); + rejectNonPrintableAsciiCharactersInFieldName(request.getContextPath(), "contextPath"); + return new FirewalledRequest(request) { @Override public void reset() { @@ -351,6 +356,13 @@ public class StrictHttpFirewall implements HttpFirewall { }; } + private void rejectNonPrintableAsciiCharactersInFieldName(String toCheck, String propertyName) { + if (!containsOnlyPrintableAsciiCharacters(toCheck)) { + throw new RequestRejectedException( + String.format("The %s was rejected because it can only contain printable ASCII characters.", propertyName)); + } + } + private void rejectForbiddenHttpMethod(HttpServletRequest request) { if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) { return; @@ -434,6 +446,9 @@ public class StrictHttpFirewall implements HttpFirewall { } private static boolean containsOnlyPrintableAsciiCharacters(String uri) { + if (uri == null) { + return true; + } int length = uri.length(); for (int i = 0; i < length; i++) { char c = uri.charAt(i); diff --git a/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java b/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java index 8695ea7fa6..d91818af2a 100644 --- a/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java +++ b/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java @@ -18,6 +18,7 @@ package org.springframework.security.web.firewall; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.fail; import java.util.Arrays; @@ -379,6 +380,34 @@ public class StrictHttpFirewallTests { // --- from DefaultHttpFirewallTests --- + @Test + public void getFirewalledRequestWhenContainsLineFeedThenException() { + this.request.setRequestURI("/something\n/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsLineFeedThenException() { + this.request.setServletPath("/something\n/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsCarriageReturnThenException() { + this.request.setRequestURI("/something\r/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsCarriageReturnThenException() { + this.request.setServletPath("/something\r/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + /** * On WebSphere 8.5 a URL like /context-root/a/b;%2f1/c can bypass a rule on * /a/b/c because the pathInfo is /a/b;/1/c which ends up being /a/b/1/c From 0dcb592b03c229874f5a560c26c39b5941a88b33 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 16 May 2022 09:48:42 -0500 Subject: [PATCH 347/348] AntRegexRequestMatcher Optimization Closes gh-11234 --- .../web/util/matcher/RegexRequestMatcher.java | 15 +++++++-------- .../util/matcher/RegexRequestMatcherTests.java | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java b/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java index 1fe184dbaf..890297e88f 100644 --- a/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java +++ b/web/src/main/java/org/springframework/security/web/util/matcher/RegexRequestMatcher.java @@ -40,8 +40,13 @@ import org.springframework.util.StringUtils; * @since 3.1 */ public final class RegexRequestMatcher implements RequestMatcher { + private final static Log logger = LogFactory.getLog(RegexRequestMatcher.class); + private static final int DEFAULT = Pattern.DOTALL; + + private static final int CASE_INSENSITIVE = DEFAULT | Pattern.CASE_INSENSITIVE; + private final Pattern pattern; private final HttpMethod httpMethod; @@ -64,14 +69,8 @@ public final class RegexRequestMatcher implements RequestMatcher { * {@link Pattern#CASE_INSENSITIVE} flag set. */ public RegexRequestMatcher(String pattern, String httpMethod, boolean caseInsensitive) { - if (caseInsensitive) { - this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); - } - else { - this.pattern = Pattern.compile(pattern); - } - this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod - .valueOf(httpMethod) : null; + this.pattern = Pattern.compile(pattern, caseInsensitive ? CASE_INSENSITIVE : DEFAULT); + this.httpMethod = StringUtils.hasText(httpMethod) ? HttpMethod.valueOf(httpMethod) : null; } /** diff --git a/web/src/test/java/org/springframework/security/web/util/matcher/RegexRequestMatcherTests.java b/web/src/test/java/org/springframework/security/web/util/matcher/RegexRequestMatcherTests.java index b6c457d63c..71baef4ced 100644 --- a/web/src/test/java/org/springframework/security/web/util/matcher/RegexRequestMatcherTests.java +++ b/web/src/test/java/org/springframework/security/web/util/matcher/RegexRequestMatcherTests.java @@ -108,6 +108,22 @@ public class RegexRequestMatcherTests { assertThat(matcher.matches(request)).isFalse(); } + @Test + public void matchesWithCarriageReturn() { + RegexRequestMatcher matcher = new RegexRequestMatcher(".*", null); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/blah%0a"); + request.setServletPath("/blah\n"); + assertThat(matcher.matches(request)).isTrue(); + } + + @Test + public void matchesWithLineFeed() { + RegexRequestMatcher matcher = new RegexRequestMatcher(".*", null); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/blah%0d"); + request.setServletPath("/blah\r"); + assertThat(matcher.matches(request)).isTrue(); + } + @Test public void toStringThenFormatted() { RegexRequestMatcher matcher = new RegexRequestMatcher("/blah", "GET"); From cfc057b62967429cb1a064d62f5a17b4cd7f6dfd Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 17 May 2022 15:53:18 -0500 Subject: [PATCH 348/348] StrictHttpFirewall allows CJKV characters Closes gh-11264 --- .../web/firewall/StrictHttpFirewall.java | 703 +++++++++++++----- .../web/firewall/StrictHttpFirewallTests.java | 588 +++++++++++---- 2 files changed, 976 insertions(+), 315 deletions(-) diff --git a/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java b/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java index aef2a7b533..18a1e01741 100644 --- a/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java +++ b/web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -19,14 +19,19 @@ package org.springframework.security.web.firewall; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Enumeration; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Predicate; +import java.util.regex.Pattern; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; /** *

    @@ -37,93 +42,127 @@ import org.springframework.http.HttpMethod; * The following rules are applied to the firewall: *

    *
      - *
    • - * Rejects HTTP methods that are not allowed. This specified to block - * HTTP Verb tampering and XST attacks. - * See {@link #setAllowedHttpMethods(Collection)} - *
    • - *
    • - * Rejects URLs that are not normalized to avoid bypassing security constraints. There is - * no way to disable this as it is considered extremely risky to disable this constraint. - * A few options to allow this behavior is to normalize the request prior to the firewall - * or using {@link DefaultHttpFirewall} instead. Please keep in mind that normalizing the - * request is fragile and why requests are rejected rather than normalized. - *
    • - *
    • - * Rejects URLs that contain characters that are not printable ASCII characters. There is - * no way to disable this as it is considered extremely risky to disable this constraint. - *
    • - *
    • - * Rejects URLs that contain semicolons. See {@link #setAllowSemicolon(boolean)} - *
    • - *
    • - * Rejects URLs that contain a URL encoded slash. See - * {@link #setAllowUrlEncodedSlash(boolean)} - *
    • - *
    • - * Rejects URLs that contain a backslash. See {@link #setAllowBackSlash(boolean)} - *
    • - *
    • - * Rejects URLs that contain a URL encoded percent. See - * {@link #setAllowUrlEncodedPercent(boolean)} - *
    • - *
    • - * Rejects hosts that are not allowed. See - * {@link #setAllowedHostnames(Predicate)} + *
    • Rejects HTTP methods that are not allowed. This specified to block + * HTTP Verb + * tampering and XST attacks. See {@link #setAllowedHttpMethods(Collection)}
    • + *
    • Rejects URLs that are not normalized to avoid bypassing security constraints. There + * is no way to disable this as it is considered extremely risky to disable this + * constraint. A few options to allow this behavior is to normalize the request prior to + * the firewall or using {@link DefaultHttpFirewall} instead. Please keep in mind that + * normalizing the request is fragile and why requests are rejected rather than + * normalized.
    • + *
    • Rejects URLs that contain characters that are not printable ASCII characters. There + * is no way to disable this as it is considered extremely risky to disable this + * constraint.
    • + *
    • Rejects URLs that contain semicolons. See {@link #setAllowSemicolon(boolean)}
    • + *
    • Rejects URLs that contain a URL encoded slash. See + * {@link #setAllowUrlEncodedSlash(boolean)}
    • + *
    • Rejects URLs that contain a backslash. See {@link #setAllowBackSlash(boolean)}
    • + *
    • Rejects URLs that contain a null character. See {@link #setAllowNull(boolean)}
    • + *
    • Rejects URLs that contain a URL encoded percent. See + * {@link #setAllowUrlEncodedPercent(boolean)}
    • + *
    • Rejects hosts that are not allowed. See {@link #setAllowedHostnames(Predicate)} *
    • + *
    • Reject headers names that are not allowed. See + * {@link #setAllowedHeaderNames(Predicate)}
    • + *
    • Reject headers values that are not allowed. See + * {@link #setAllowedHeaderValues(Predicate)}
    • + *
    • Reject parameter names that are not allowed. See + * {@link #setAllowedParameterNames(Predicate)}
    • + *
    • Reject parameter values that are not allowed. See + * {@link #setAllowedParameterValues(Predicate)}
    • *
    * - * @see DefaultHttpFirewall * @author Rob Winch * @author EddĂș MelĂ©ndez * @since 4.2.4 + * @see DefaultHttpFirewall */ public class StrictHttpFirewall implements HttpFirewall { + /** - * Used to specify to {@link #setAllowedHttpMethods(Collection)} that any HTTP method should be allowed. + * Used to specify to {@link #setAllowedHttpMethods(Collection)} that any HTTP method + * should be allowed. */ - private static final Set ALLOW_ANY_HTTP_METHOD = Collections.unmodifiableSet(Collections.emptySet()); + private static final Set ALLOW_ANY_HTTP_METHOD = Collections.emptySet(); private static final String ENCODED_PERCENT = "%25"; private static final String PERCENT = "%"; - private static final List FORBIDDEN_ENCODED_PERIOD = Collections.unmodifiableList(Arrays.asList("%2e", "%2E")); + private static final List FORBIDDEN_ENCODED_PERIOD = Collections + .unmodifiableList(Arrays.asList("%2e", "%2E")); - private static final List FORBIDDEN_SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B")); + private static final List FORBIDDEN_SEMICOLON = Collections + .unmodifiableList(Arrays.asList(";", "%3b", "%3B")); - private static final List FORBIDDEN_FORWARDSLASH = Collections.unmodifiableList(Arrays.asList("%2f", "%2F")); + private static final List FORBIDDEN_FORWARDSLASH = Collections + .unmodifiableList(Arrays.asList("%2f", "%2F")); - private static final List FORBIDDEN_DOUBLE_FORWARDSLASH = Collections.unmodifiableList(Arrays.asList("//", "%2f%2f", "%2f%2F", "%2F%2f", "%2F%2F")); + private static final List FORBIDDEN_DOUBLE_FORWARDSLASH = Collections + .unmodifiableList(Arrays.asList("//", "%2f%2f", "%2f%2F", "%2F%2f", "%2F%2F")); - private static final List FORBIDDEN_BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C")); + private static final List FORBIDDEN_BACKSLASH = Collections + .unmodifiableList(Arrays.asList("\\", "%5c", "%5C")); - private Set encodedUrlBlacklist = new HashSet<>(); + private static final List FORBIDDEN_NULL = Collections.unmodifiableList(Arrays.asList("\0", "%00")); - private Set decodedUrlBlacklist = new HashSet<>(); + private static final List FORBIDDEN_LF = Collections.unmodifiableList(Arrays.asList("\n", "%0a", "%0A")); + + private static final List FORBIDDEN_CR = Collections.unmodifiableList(Arrays.asList("\r", "%0d", "%0D")); + + private static final List FORBIDDEN_LINE_SEPARATOR = Collections.unmodifiableList(Arrays.asList("\u2028")); + + private static final List FORBIDDEN_PARAGRAPH_SEPARATOR = Collections + .unmodifiableList(Arrays.asList("\u2029")); + + private Set encodedUrlBlocklist = new HashSet<>(); + + private Set decodedUrlBlocklist = new HashSet<>(); private Set allowedHttpMethods = createDefaultAllowedHttpMethods(); - private Predicate allowedHostnames = hostname -> true; + private Predicate allowedHostnames = (hostname) -> true; + + private static final Pattern ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN = Pattern + .compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*"); + + private static final Predicate ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE = ( + s) -> ASSIGNED_AND_NOT_ISO_CONTROL_PATTERN.matcher(s).matches(); + + private Predicate allowedHeaderNames = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE; + + private Predicate allowedHeaderValues = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE; + + private Predicate allowedParameterNames = ASSIGNED_AND_NOT_ISO_CONTROL_PREDICATE; + + private Predicate allowedParameterValues = (value) -> true; public StrictHttpFirewall() { - urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); - urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH); - urlBlacklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH); - urlBlacklistsAddAll(FORBIDDEN_BACKSLASH); + urlBlocklistsAddAll(FORBIDDEN_SEMICOLON); + urlBlocklistsAddAll(FORBIDDEN_FORWARDSLASH); + urlBlocklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH); + urlBlocklistsAddAll(FORBIDDEN_BACKSLASH); + urlBlocklistsAddAll(FORBIDDEN_NULL); + urlBlocklistsAddAll(FORBIDDEN_LF); + urlBlocklistsAddAll(FORBIDDEN_CR); - this.encodedUrlBlacklist.add(ENCODED_PERCENT); - this.encodedUrlBlacklist.addAll(FORBIDDEN_ENCODED_PERIOD); - this.decodedUrlBlacklist.add(PERCENT); + this.encodedUrlBlocklist.add(ENCODED_PERCENT); + this.encodedUrlBlocklist.addAll(FORBIDDEN_ENCODED_PERIOD); + this.decodedUrlBlocklist.add(PERCENT); + this.decodedUrlBlocklist.addAll(FORBIDDEN_LINE_SEPARATOR); + this.decodedUrlBlocklist.addAll(FORBIDDEN_PARAGRAPH_SEPARATOR); } /** - * Sets if any HTTP method is allowed. If this set to true, then no validation on the HTTP method will be performed. - * This can open the application up to - * HTTP Verb tampering and XST attacks - * @param unsafeAllowAnyHttpMethod if true, disables HTTP method validation, else resets back to the defaults. Default is false. - * @see #setAllowedHttpMethods(Collection) + * Sets if any HTTP method is allowed. If this set to true, then no validation on the + * HTTP method will be performed. This can open the application up to + * HTTP + * Verb tampering and XST attacks + * @param unsafeAllowAnyHttpMethod if true, disables HTTP method validation, else + * resets back to the defaults. Default is false. * @since 5.1 + * @see #setAllowedHttpMethods(Collection) */ public void setUnsafeAllowAnyHttpMethod(boolean unsafeAllowAnyHttpMethod) { this.allowedHttpMethods = unsafeAllowAnyHttpMethod ? ALLOW_ANY_HTTP_METHOD : createDefaultAllowedHttpMethods(); @@ -131,40 +170,36 @@ public class StrictHttpFirewall implements HttpFirewall { /** *

    - * Determines which HTTP methods should be allowed. The default is to allow "DELETE", "GET", "HEAD", "OPTIONS", - * "PATCH", "POST", and "PUT". + * Determines which HTTP methods should be allowed. The default is to allow "DELETE", + * "GET", "HEAD", "OPTIONS", "PATCH", "POST", and "PUT". *

    - * - * @param allowedHttpMethods the case-sensitive collection of HTTP methods that are allowed. - * @see #setUnsafeAllowAnyHttpMethod(boolean) + * @param allowedHttpMethods the case-sensitive collection of HTTP methods that are + * allowed. * @since 5.1 + * @see #setUnsafeAllowAnyHttpMethod(boolean) */ public void setAllowedHttpMethods(Collection allowedHttpMethods) { - if (allowedHttpMethods == null) { - throw new IllegalArgumentException("allowedHttpMethods cannot be null"); - } - if (allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) { - this.allowedHttpMethods = ALLOW_ANY_HTTP_METHOD; - } else { - this.allowedHttpMethods = new HashSet<>(allowedHttpMethods); - } + Assert.notNull(allowedHttpMethods, "allowedHttpMethods cannot be null"); + this.allowedHttpMethods = (allowedHttpMethods != ALLOW_ANY_HTTP_METHOD) ? new HashSet<>(allowedHttpMethods) + : ALLOW_ANY_HTTP_METHOD; } /** *

    * Determines if semicolon is allowed in the URL (i.e. matrix variables). The default * is to disable this behavior because it is a common way of attempting to perform - * Reflected File Download Attacks. - * It is also the source of many exploits which bypass URL based security. + * Reflected File + * Download Attacks. It is also the source of many exploits which bypass URL based + * security. *

    - *

    For example, the following CVEs are a subset of the issues related - * to ambiguities in the Servlet Specification on how to treat semicolons that - * led to CVEs: + *

    + * For example, the following CVEs are a subset of the issues related to ambiguities + * in the Servlet Specification on how to treat semicolons that led to CVEs: *

    * * *

    @@ -177,17 +212,16 @@ public class StrictHttpFirewall implements HttpFirewall { * any sensitive information) in a URL as it can lead to leaking. Instead use Cookies. * *

  • Matrix Variables - Users wanting to leverage Matrix Variables should consider - * using HTTP parameters instead. - *
  • + * using HTTP parameters instead. * - * * @param allowSemicolon should semicolons be allowed in the URL. Default is false */ public void setAllowSemicolon(boolean allowSemicolon) { if (allowSemicolon) { - urlBlacklistsRemoveAll(FORBIDDEN_SEMICOLON); - } else { - urlBlacklistsAddAll(FORBIDDEN_SEMICOLON); + urlBlocklistsRemoveAll(FORBIDDEN_SEMICOLON); + } + else { + urlBlocklistsAddAll(FORBIDDEN_SEMICOLON); } } @@ -202,32 +236,32 @@ public class StrictHttpFirewall implements HttpFirewall { * parsed consistently which results in different values in {@code HttpServletRequest} * path related values which allow bypassing certain security constraints. *

    - * * @param allowUrlEncodedSlash should a slash "/" that is URL encoded "%2F" be allowed * in the path or not. Default is false. */ public void setAllowUrlEncodedSlash(boolean allowUrlEncodedSlash) { if (allowUrlEncodedSlash) { - urlBlacklistsRemoveAll(FORBIDDEN_FORWARDSLASH); - } else { - urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH); + urlBlocklistsRemoveAll(FORBIDDEN_FORWARDSLASH); + } + else { + urlBlocklistsAddAll(FORBIDDEN_FORWARDSLASH); } } /** *

    - * Determines if double slash "//" that is URL encoded "%2F%2F" should be allowed in the path or - * not. The default is to not allow. + * Determines if double slash "//" that is URL encoded "%2F%2F" should be allowed in + * the path or not. The default is to not allow. *

    - * - * @param allowUrlEncodedDoubleSlash should a slash "//" that is URL encoded "%2F%2F" be allowed - * in the path or not. Default is false. + * @param allowUrlEncodedDoubleSlash should a slash "//" that is URL encoded "%2F%2F" + * be allowed in the path or not. Default is false. */ public void setAllowUrlEncodedDoubleSlash(boolean allowUrlEncodedDoubleSlash) { if (allowUrlEncodedDoubleSlash) { - urlBlacklistsRemoveAll(FORBIDDEN_DOUBLE_FORWARDSLASH); - } else { - urlBlacklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH); + urlBlocklistsRemoveAll(FORBIDDEN_DOUBLE_FORWARDSLASH); + } + else { + urlBlocklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH); } } @@ -240,19 +274,19 @@ public class StrictHttpFirewall implements HttpFirewall { *

    * For example, due to ambiguities in the servlet specification a URL encoded period * might lead to bypassing security constraints through a directory traversal attack. - * This is because the path is not parsed consistently which results in different + * This is because the path is not parsed consistently which results in different * values in {@code HttpServletRequest} path related values which allow bypassing * certain security constraints. *

    - * * @param allowUrlEncodedPeriod should a period "." that is URL encoded "%2E" be * allowed in the path or not. Default is false. */ public void setAllowUrlEncodedPeriod(boolean allowUrlEncodedPeriod) { if (allowUrlEncodedPeriod) { - this.encodedUrlBlacklist.removeAll(FORBIDDEN_ENCODED_PERIOD); - } else { - this.encodedUrlBlacklist.addAll(FORBIDDEN_ENCODED_PERIOD); + this.encodedUrlBlocklist.removeAll(FORBIDDEN_ENCODED_PERIOD); + } + else { + this.encodedUrlBlocklist.addAll(FORBIDDEN_ENCODED_PERIOD); } } @@ -265,19 +299,38 @@ public class StrictHttpFirewall implements HttpFirewall { *

    * For example, due to ambiguities in the servlet specification a URL encoded period * might lead to bypassing security constraints through a directory traversal attack. - * This is because the path is not parsed consistently which results in different + * This is because the path is not parsed consistently which results in different * values in {@code HttpServletRequest} path related values which allow bypassing * certain security constraints. *

    - * * @param allowBackSlash a backslash "\" or a URL encoded backslash "%5C" be allowed * in the path or not. Default is false */ public void setAllowBackSlash(boolean allowBackSlash) { if (allowBackSlash) { - urlBlacklistsRemoveAll(FORBIDDEN_BACKSLASH); - } else { - urlBlacklistsAddAll(FORBIDDEN_BACKSLASH); + urlBlocklistsRemoveAll(FORBIDDEN_BACKSLASH); + } + else { + urlBlocklistsAddAll(FORBIDDEN_BACKSLASH); + } + } + + /** + *

    + * Determines if a null "\0" or a URL encoded nul "%00" should be allowed in the path + * or not. The default is not to allow this behavior because it is a frequent source + * of security exploits. + *

    + * @param allowNull a null "\0" or a URL encoded null "%00" be allowed in the path or + * not. Default is false + * @since 5.3.14 + */ + public void setAllowNull(boolean allowNull) { + if (allowNull) { + urlBlocklistsRemoveAll(FORBIDDEN_NULL); + } + else { + urlBlocklistsAddAll(FORBIDDEN_NULL); } } @@ -291,75 +344,177 @@ public class StrictHttpFirewall implements HttpFirewall { * For example, this can lead to exploits that involve double URL encoding that lead * to bypassing security constraints. *

    - * * @param allowUrlEncodedPercent if a percent "%" that is URL encoded "%25" should be * allowed in the path or not. Default is false */ public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) { if (allowUrlEncodedPercent) { - this.encodedUrlBlacklist.remove(ENCODED_PERCENT); - this.decodedUrlBlacklist.remove(PERCENT); - } else { - this.encodedUrlBlacklist.add(ENCODED_PERCENT); - this.decodedUrlBlacklist.add(PERCENT); + this.encodedUrlBlocklist.remove(ENCODED_PERCENT); + this.decodedUrlBlocklist.remove(PERCENT); } + else { + this.encodedUrlBlocklist.add(ENCODED_PERCENT); + this.decodedUrlBlocklist.add(PERCENT); + } + } + + /** + * Determines if a URL encoded Carriage Return is allowed in the path or not. The + * default is not to allow this behavior because it is a frequent source of security + * exploits. + * @param allowUrlEncodedCarriageReturn if URL encoded Carriage Return is allowed in + * the URL or not. Default is false. + */ + public void setAllowUrlEncodedCarriageReturn(boolean allowUrlEncodedCarriageReturn) { + if (allowUrlEncodedCarriageReturn) { + urlBlocklistsRemoveAll(FORBIDDEN_CR); + } + else { + urlBlocklistsAddAll(FORBIDDEN_CR); + } + } + + /** + * Determines if a URL encoded Line Feed is allowed in the path or not. The default is + * not to allow this behavior because it is a frequent source of security exploits. + * @param allowUrlEncodedLineFeed if URL encoded Line Feed is allowed in the URL or + * not. Default is false. + */ + public void setAllowUrlEncodedLineFeed(boolean allowUrlEncodedLineFeed) { + if (allowUrlEncodedLineFeed) { + urlBlocklistsRemoveAll(FORBIDDEN_LF); + } + else { + urlBlocklistsAddAll(FORBIDDEN_LF); + } + } + + /** + * Determines if a URL encoded paragraph separator is allowed in the path or not. The + * default is not to allow this behavior because it is a frequent source of security + * exploits. + * @param allowUrlEncodedParagraphSeparator if URL encoded paragraph separator is + * allowed in the URL or not. Default is false. + */ + public void setAllowUrlEncodedParagraphSeparator(boolean allowUrlEncodedParagraphSeparator) { + if (allowUrlEncodedParagraphSeparator) { + this.decodedUrlBlocklist.removeAll(FORBIDDEN_PARAGRAPH_SEPARATOR); + } + else { + this.decodedUrlBlocklist.addAll(FORBIDDEN_PARAGRAPH_SEPARATOR); + } + } + + /** + * Determines if a URL encoded line separator is allowed in the path or not. The + * default is not to allow this behavior because it is a frequent source of security + * exploits. + * @param allowUrlEncodedLineSeparator if URL encoded line separator is allowed in the + * URL or not. Default is false. + */ + public void setAllowUrlEncodedLineSeparator(boolean allowUrlEncodedLineSeparator) { + if (allowUrlEncodedLineSeparator) { + this.decodedUrlBlocklist.removeAll(FORBIDDEN_LINE_SEPARATOR); + } + else { + this.decodedUrlBlocklist.addAll(FORBIDDEN_LINE_SEPARATOR); + } + } + + /** + *

    + * Determines which header names should be allowed. The default is to reject header + * names that contain ISO control characters and characters that are not defined. + *

    + * @param allowedHeaderNames the predicate for testing header names + * @since 5.3.14 + * @see Character#isISOControl(int) + * @see Character#isDefined(int) + */ + public void setAllowedHeaderNames(Predicate allowedHeaderNames) { + Assert.notNull(allowedHeaderNames, "allowedHeaderNames cannot be null"); + this.allowedHeaderNames = allowedHeaderNames; + } + + /** + *

    + * Determines which header values should be allowed. The default is to reject header + * values that contain ISO control characters and characters that are not defined. + *

    + * @param allowedHeaderValues the predicate for testing hostnames + * @since 5.3.14 + * @see Character#isISOControl(int) + * @see Character#isDefined(int) + */ + public void setAllowedHeaderValues(Predicate allowedHeaderValues) { + Assert.notNull(allowedHeaderValues, "allowedHeaderValues cannot be null"); + this.allowedHeaderValues = allowedHeaderValues; + } + + /** + * Determines which parameter names should be allowed. The default is to reject header + * names that contain ISO control characters and characters that are not defined. + * @param allowedParameterNames the predicate for testing parameter names + * @since 5.3.14 + * @see Character#isISOControl(int) + * @see Character#isDefined(int) + */ + public void setAllowedParameterNames(Predicate allowedParameterNames) { + Assert.notNull(allowedParameterNames, "allowedParameterNames cannot be null"); + this.allowedParameterNames = allowedParameterNames; + } + + /** + *

    + * Determines which parameter values should be allowed. The default is to allow any + * parameter value. + *

    + * @param allowedParameterValues the predicate for testing parameter values + * @since 5.3.14 + */ + public void setAllowedParameterValues(Predicate allowedParameterValues) { + Assert.notNull(allowedParameterValues, "allowedParameterValues cannot be null"); + this.allowedParameterValues = allowedParameterValues; } /** *

    * Determines which hostnames should be allowed. The default is to allow any hostname. *

    - * * @param allowedHostnames the predicate for testing hostnames * @since 5.2 */ public void setAllowedHostnames(Predicate allowedHostnames) { - if (allowedHostnames == null) { - throw new IllegalArgumentException("allowedHostnames cannot be null"); - } + Assert.notNull(allowedHostnames, "allowedHostnames cannot be null"); this.allowedHostnames = allowedHostnames; } - private void urlBlacklistsAddAll(Collection values) { - this.encodedUrlBlacklist.addAll(values); - this.decodedUrlBlacklist.addAll(values); + private void urlBlocklistsAddAll(Collection values) { + this.encodedUrlBlocklist.addAll(values); + this.decodedUrlBlocklist.addAll(values); } - private void urlBlacklistsRemoveAll(Collection values) { - this.encodedUrlBlacklist.removeAll(values); - this.decodedUrlBlacklist.removeAll(values); + private void urlBlocklistsRemoveAll(Collection values) { + this.encodedUrlBlocklist.removeAll(values); + this.decodedUrlBlocklist.removeAll(values); } @Override public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { rejectForbiddenHttpMethod(request); - rejectedBlacklistedUrls(request); + rejectedBlocklistedUrls(request); rejectedUntrustedHosts(request); - if (!isNormalized(request)) { throw new RequestRejectedException("The request was rejected because the URL was not normalized."); } - - String requestUri = request.getRequestURI(); - if (!containsOnlyPrintableAsciiCharacters(requestUri)) { - throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters."); - } rejectNonPrintableAsciiCharactersInFieldName(request.getRequestURI(), "requestURI"); - rejectNonPrintableAsciiCharactersInFieldName(request.getServletPath(), "servletPath"); - rejectNonPrintableAsciiCharactersInFieldName(request.getPathInfo(), "pathInfo"); - rejectNonPrintableAsciiCharactersInFieldName(request.getContextPath(), "contextPath"); - - return new FirewalledRequest(request) { - @Override - public void reset() { - } - }; + return new StrictFirewalledRequest(request); } private void rejectNonPrintableAsciiCharactersInFieldName(String toCheck, String propertyName) { if (!containsOnlyPrintableAsciiCharacters(toCheck)) { - throw new RequestRejectedException( - String.format("The %s was rejected because it can only contain printable ASCII characters.", propertyName)); + throw new RequestRejectedException(String.format( + "The %s was rejected because it can only contain printable ASCII characters.", propertyName)); } } @@ -368,22 +523,25 @@ public class StrictHttpFirewall implements HttpFirewall { return; } if (!this.allowedHttpMethods.contains(request.getMethod())) { - throw new RequestRejectedException("The request was rejected because the HTTP method \"" + - request.getMethod() + - "\" was not included within the whitelist " + - this.allowedHttpMethods); + throw new RequestRejectedException( + "The request was rejected because the HTTP method \"" + request.getMethod() + + "\" was not included within the list of allowed HTTP methods " + this.allowedHttpMethods); } } - private void rejectedBlacklistedUrls(HttpServletRequest request) { - for (String forbidden : this.encodedUrlBlacklist) { + private void rejectedBlocklistedUrls(HttpServletRequest request) { + for (String forbidden : this.encodedUrlBlocklist) { if (encodedUrlContains(request, forbidden)) { - throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\""); + throw new RequestRejectedException( + "The request was rejected because the URL contained a potentially malicious String \"" + + forbidden + "\""); } } - for (String forbidden : this.decodedUrlBlacklist) { + for (String forbidden : this.decodedUrlBlocklist) { if (decodedUrlContains(request, forbidden)) { - throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\""); + throw new RequestRejectedException( + "The request was rejected because the URL contained a potentially malicious String \"" + + forbidden + "\""); } } } @@ -391,7 +549,8 @@ public class StrictHttpFirewall implements HttpFirewall { private void rejectedUntrustedHosts(HttpServletRequest request) { String serverName = request.getServerName(); if (serverName != null && !this.allowedHostnames.test(serverName)) { - throw new RequestRejectedException("The request was rejected because the domain " + serverName + " is untrusted."); + throw new RequestRejectedException( + "The request was rejected because the domain " + serverName + " is untrusted."); } } @@ -451,12 +610,11 @@ public class StrictHttpFirewall implements HttpFirewall { } int length = uri.length(); for (int i = 0; i < length; i++) { - char c = uri.charAt(i); - if (c < '\u0020' || c > '\u007e') { + char ch = uri.charAt(i); + if (ch < '\u0020' || ch > '\u007e') { return false; } } - return true; } @@ -465,51 +623,236 @@ public class StrictHttpFirewall implements HttpFirewall { } /** - * Checks whether a path is normalized (doesn't contain path traversal - * sequences like "./", "/../" or "/.") - * - * @param path - * the path to test - * @return true if the path doesn't contain any path-traversal character - * sequences. + * Checks whether a path is normalized (doesn't contain path traversal sequences like + * "./", "/../" or "/.") + * @param path the path to test + * @return true if the path doesn't contain any path-traversal character sequences. */ private static boolean isNormalized(String path) { if (path == null) { return true; } - - for (int j = path.length(); j > 0;) { - int i = path.lastIndexOf('/', j - 1); - int gap = j - i; - - if (gap == 2 && path.charAt(i + 1) == '.') { - // ".", "/./" or "/." - return false; - } else if (gap == 3 && path.charAt(i + 1) == '.' && path.charAt(i + 2) == '.') { + for (int i = path.length(); i > 0;) { + int slashIndex = path.lastIndexOf('/', i - 1); + int gap = i - slashIndex; + if (gap == 2 && path.charAt(slashIndex + 1) == '.') { + return false; // ".", "/./" or "/." + } + if (gap == 3 && path.charAt(slashIndex + 1) == '.' && path.charAt(slashIndex + 2) == '.') { return false; } - - j = i; + i = slashIndex; } - return true; } /** - * Provides the existing encoded url blacklist which can add/remove entries from - * - * @return the existing encoded url blacklist, never null + * Provides the existing encoded url blocklist which can add/remove entries from + * @return the existing encoded url blocklist, never null */ - public Set getEncodedUrlBlacklist() { - return encodedUrlBlacklist; + public Set getEncodedUrlBlocklist() { + return this.encodedUrlBlocklist; } /** - * Provides the existing decoded url blacklist which can add/remove entries from + * Provides the existing decoded url blocklist which can add/remove entries from + * @return the existing decoded url blocklist, never null + */ + public Set getDecodedUrlBlocklist() { + return this.decodedUrlBlocklist; + } + + /** + * Provides the existing encoded url blocklist which can add/remove entries from + * @return the existing encoded url blocklist, never null + * @deprecated Use {@link #getEncodedUrlBlocklist()} instead + */ + @Deprecated + public Set getEncodedUrlBlacklist() { + return getEncodedUrlBlocklist(); + } + + /** + * Provides the existing decoded url blocklist which can add/remove entries from + * @return the existing decoded url blocklist, never null * - * @return the existing decoded url blacklist, never null */ public Set getDecodedUrlBlacklist() { - return decodedUrlBlacklist; + return getDecodedUrlBlocklist(); } + + /** + * Strict {@link FirewalledRequest}. + */ + private class StrictFirewalledRequest extends FirewalledRequest { + + StrictFirewalledRequest(HttpServletRequest request) { + super(request); + } + + @Override + public long getDateHeader(String name) { + if (name != null) { + validateAllowedHeaderName(name); + } + return super.getDateHeader(name); + } + + @Override + public int getIntHeader(String name) { + if (name != null) { + validateAllowedHeaderName(name); + } + return super.getIntHeader(name); + } + + @Override + public String getHeader(String name) { + if (name != null) { + validateAllowedHeaderName(name); + } + String value = super.getHeader(name); + if (value != null) { + validateAllowedHeaderValue(value); + } + return value; + } + + @Override + public Enumeration getHeaders(String name) { + if (name != null) { + validateAllowedHeaderName(name); + } + Enumeration headers = super.getHeaders(name); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return headers.hasMoreElements(); + } + + @Override + public String nextElement() { + String value = headers.nextElement(); + validateAllowedHeaderValue(value); + return value; + } + + }; + } + + @Override + public Enumeration getHeaderNames() { + Enumeration names = super.getHeaderNames(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return names.hasMoreElements(); + } + + @Override + public String nextElement() { + String headerNames = names.nextElement(); + validateAllowedHeaderName(headerNames); + return headerNames; + } + + }; + } + + @Override + public String getParameter(String name) { + if (name != null) { + validateAllowedParameterName(name); + } + String value = super.getParameter(name); + if (value != null) { + validateAllowedParameterValue(value); + } + return value; + } + + @Override + public Map getParameterMap() { + Map parameterMap = super.getParameterMap(); + for (Map.Entry entry : parameterMap.entrySet()) { + String name = entry.getKey(); + String[] values = entry.getValue(); + validateAllowedParameterName(name); + for (String value : values) { + validateAllowedParameterValue(value); + } + } + return parameterMap; + } + + @Override + public Enumeration getParameterNames() { + Enumeration paramaterNames = super.getParameterNames(); + return new Enumeration() { + + @Override + public boolean hasMoreElements() { + return paramaterNames.hasMoreElements(); + } + + @Override + public String nextElement() { + String name = paramaterNames.nextElement(); + validateAllowedParameterName(name); + return name; + } + + }; + } + + @Override + public String[] getParameterValues(String name) { + if (name != null) { + validateAllowedParameterName(name); + } + String[] values = super.getParameterValues(name); + if (values != null) { + for (String value : values) { + validateAllowedParameterValue(value); + } + } + return values; + } + + private void validateAllowedHeaderName(String headerNames) { + if (!StrictHttpFirewall.this.allowedHeaderNames.test(headerNames)) { + throw new RequestRejectedException( + "The request was rejected because the header name \"" + headerNames + "\" is not allowed."); + } + } + + private void validateAllowedHeaderValue(String value) { + if (!StrictHttpFirewall.this.allowedHeaderValues.test(value)) { + throw new RequestRejectedException( + "The request was rejected because the header value \"" + value + "\" is not allowed."); + } + } + + private void validateAllowedParameterName(String name) { + if (!StrictHttpFirewall.this.allowedParameterNames.test(name)) { + throw new RequestRejectedException( + "The request was rejected because the parameter name \"" + name + "\" is not allowed."); + } + } + + private void validateAllowedParameterValue(String value) { + if (!StrictHttpFirewall.this.allowedParameterValues.test(value)) { + throw new RequestRejectedException( + "The request was rejected because the parameter value \"" + value + "\" is not allowed."); + } + } + + @Override + public void reset() { + } + + }; + } diff --git a/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java b/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java index d91818af2a..809f1cc3f1 100644 --- a/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java +++ b/web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -16,26 +16,27 @@ package org.springframework.security.web.firewall; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.fail; - import java.util.Arrays; import java.util.List; +import javax.servlet.http.HttpServletRequest; + import org.junit.Test; + import org.springframework.http.HttpMethod; import org.springframework.mock.web.MockHttpServletRequest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + /** * @author Rob Winch * @author EddĂș MelĂ©ndez */ public class StrictHttpFirewallTests { - public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.", - "./path", ".//path", ".", "//path", "//path/path", "//path//path", "/path//path" }; + public String[] unnormalizedPaths = { "/..", "/./path/", "/path/path/.", "/path/path//.", "./path/../path//.", + "./path", ".//path", ".", "//path", "//path/path", "//path//path", "/path//path" }; private StrictHttpFirewall firewall = new StrictHttpFirewall(); @@ -44,32 +45,32 @@ public class StrictHttpFirewallTests { @Test public void getFirewalledRequestWhenInvalidMethodThenThrowsRequestRejectedException() { this.request.setMethod("INVALID"); - assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request)) - .isInstanceOf(RequestRejectedException.class); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } // blocks XST attacks @Test public void getFirewalledRequestWhenTraceMethodThenThrowsRequestRejectedException() { this.request.setMethod(HttpMethod.TRACE.name()); - assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request)) - .isInstanceOf(RequestRejectedException.class); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test // blocks XST attack if request is forwarded to a Microsoft IIS web server public void getFirewalledRequestWhenTrackMethodThenThrowsRequestRejectedException() { this.request.setMethod("TRACK"); - assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request)) - .isInstanceOf(RequestRejectedException.class); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test // HTTP methods are case sensitive public void getFirewalledRequestWhenLowercaseGetThenThrowsRequestRejectedException() { this.request.setMethod("get"); - assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request)) - .isInstanceOf(RequestRejectedException.class); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test @@ -77,8 +78,7 @@ public class StrictHttpFirewallTests { List allowedMethods = Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"); for (String allowedMethod : allowedMethods) { this.request = new MockHttpServletRequest(allowedMethod, ""); - assertThatCode(() -> this.firewall.getFirewalledRequest(this.request)) - .doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(this.request); } } @@ -86,8 +86,7 @@ public class StrictHttpFirewallTests { public void getFirewalledRequestWhenInvalidMethodAndAnyMethodThenNoException() { this.firewall.setUnsafeAllowAnyHttpMethod(true); this.request.setMethod("INVALID"); - assertThatCode(() -> this.firewall.getFirewalledRequest(this.request)) - .doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(this.request); } @Test @@ -95,11 +94,8 @@ public class StrictHttpFirewallTests { for (String path : this.unnormalizedPaths) { this.request = new MockHttpServletRequest("GET", ""); this.request.setRequestURI(path); - try { - this.firewall.getFirewalledRequest(this.request); - fail(path + " is un-normalized"); - } catch (RequestRejectedException expected) { - } + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } } @@ -108,11 +104,8 @@ public class StrictHttpFirewallTests { for (String path : this.unnormalizedPaths) { this.request = new MockHttpServletRequest("GET", ""); this.request.setContextPath(path); - try { - this.firewall.getFirewalledRequest(this.request); - fail(path + " is un-normalized"); - } catch (RequestRejectedException expected) { - } + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } } @@ -121,11 +114,8 @@ public class StrictHttpFirewallTests { for (String path : this.unnormalizedPaths) { this.request = new MockHttpServletRequest("GET", ""); this.request.setServletPath(path); - try { - this.firewall.getFirewalledRequest(this.request); - fail(path + " is un-normalized"); - } catch (RequestRejectedException expected) { - } + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } } @@ -134,105 +124,99 @@ public class StrictHttpFirewallTests { for (String path : this.unnormalizedPaths) { this.request = new MockHttpServletRequest("GET", ""); this.request.setPathInfo(path); - try { - this.firewall.getFirewalledRequest(this.request); - fail(path + " is un-normalized"); - } catch (RequestRejectedException expected) { - } + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } } - // --- ; --- - - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenSemicolonInContextPathThenThrowsRequestRejectedException() { this.request.setContextPath(";/context"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenSemicolonInServletPathThenThrowsRequestRejectedException() { this.request.setServletPath("/spring;/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenSemicolonInPathInfoThenThrowsRequestRejectedException() { this.request.setPathInfo("/path;/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenSemicolonInRequestUriThenThrowsRequestRejectedException() { this.request.setRequestURI("/path;/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenEncodedSemicolonInContextPathThenThrowsRequestRejectedException() { this.request.setContextPath("%3B/context"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenEncodedSemicolonInServletPathThenThrowsRequestRejectedException() { this.request.setServletPath("/spring%3B/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenEncodedSemicolonInPathInfoThenThrowsRequestRejectedException() { this.request.setPathInfo("/path%3B/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenEncodedSemicolonInRequestUriThenThrowsRequestRejectedException() { this.request.setRequestURI("/path%3B/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenLowercaseEncodedSemicolonInContextPathThenThrowsRequestRejectedException() { this.request.setContextPath("%3b/context"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenLowercaseEncodedSemicolonInServletPathThenThrowsRequestRejectedException() { this.request.setServletPath("/spring%3b/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenLowercaseEncodedSemicolonInPathInfoThenThrowsRequestRejectedException() { this.request.setPathInfo("/path%3b/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenLowercaseEncodedSemicolonInRequestUriThenThrowsRequestRejectedException() { this.request.setRequestURI("/path%3b/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test public void getFirewalledRequestWhenSemicolonInContextPathAndAllowSemicolonThenNoException() { this.firewall.setAllowSemicolon(true); this.request.setContextPath(";/context"); - this.firewall.getFirewalledRequest(this.request); } @@ -240,7 +224,6 @@ public class StrictHttpFirewallTests { public void getFirewalledRequestWhenSemicolonInServletPathAndAllowSemicolonThenNoException() { this.firewall.setAllowSemicolon(true); this.request.setServletPath("/spring;/"); - this.firewall.getFirewalledRequest(this.request); } @@ -248,7 +231,6 @@ public class StrictHttpFirewallTests { public void getFirewalledRequestWhenSemicolonInPathInfoAndAllowSemicolonThenNoException() { this.firewall.setAllowSemicolon(true); this.request.setPathInfo("/path;/"); - this.firewall.getFirewalledRequest(this.request); } @@ -256,7 +238,6 @@ public class StrictHttpFirewallTests { public void getFirewalledRequestWhenSemicolonInRequestUriAndAllowSemicolonThenNoException() { this.firewall.setAllowSemicolon(true); this.request.setRequestURI("/path;/"); - this.firewall.getFirewalledRequest(this.request); } @@ -265,7 +246,6 @@ public class StrictHttpFirewallTests { this.firewall.setAllowUrlEncodedPercent(true); this.firewall.setAllowSemicolon(true); this.request.setContextPath("%3B/context"); - this.firewall.getFirewalledRequest(this.request); } @@ -274,7 +254,6 @@ public class StrictHttpFirewallTests { this.firewall.setAllowUrlEncodedPercent(true); this.firewall.setAllowSemicolon(true); this.request.setServletPath("/spring%3B/"); - this.firewall.getFirewalledRequest(this.request); } @@ -283,7 +262,6 @@ public class StrictHttpFirewallTests { this.firewall.setAllowUrlEncodedPercent(true); this.firewall.setAllowSemicolon(true); this.request.setPathInfo("/path%3B/"); - this.firewall.getFirewalledRequest(this.request); } @@ -291,7 +269,6 @@ public class StrictHttpFirewallTests { public void getFirewalledRequestWhenEncodedSemicolonInRequestUriAndAllowSemicolonThenNoException() { this.firewall.setAllowSemicolon(true); this.request.setRequestURI("/path%3B/"); - this.firewall.getFirewalledRequest(this.request); } @@ -300,7 +277,6 @@ public class StrictHttpFirewallTests { this.firewall.setAllowUrlEncodedPercent(true); this.firewall.setAllowSemicolon(true); this.request.setContextPath("%3b/context"); - this.firewall.getFirewalledRequest(this.request); } @@ -309,7 +285,6 @@ public class StrictHttpFirewallTests { this.firewall.setAllowUrlEncodedPercent(true); this.firewall.setAllowSemicolon(true); this.request.setServletPath("/spring%3b/"); - this.firewall.getFirewalledRequest(this.request); } @@ -318,7 +293,6 @@ public class StrictHttpFirewallTests { this.firewall.setAllowUrlEncodedPercent(true); this.firewall.setAllowSemicolon(true); this.request.setPathInfo("/path%3b/"); - this.firewall.getFirewalledRequest(this.request); } @@ -326,38 +300,35 @@ public class StrictHttpFirewallTests { public void getFirewalledRequestWhenLowercaseEncodedSemicolonInRequestUriAndAllowSemicolonThenNoException() { this.firewall.setAllowSemicolon(true); this.request.setRequestURI("/path%3b/"); - this.firewall.getFirewalledRequest(this.request); } - // --- encoded . --- - - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenEncodedPeriodInThenThrowsRequestRejectedException() { this.request.setRequestURI("/%2E/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenLowercaseEncodedPeriodInThenThrowsRequestRejectedException() { this.request.setRequestURI("/%2e/"); - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test public void getFirewalledRequestWhenAllowEncodedPeriodAndEncodedPeriodInThenNoException() { this.firewall.setAllowUrlEncodedPeriod(true); this.request.setRequestURI("/%2E/"); - this.firewall.getFirewalledRequest(this.request); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenExceedsLowerboundAsciiThenException() { this.request.setRequestURI("/\u0019"); - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test @@ -372,13 +343,46 @@ public class StrictHttpFirewallTests { this.firewall.getFirewalledRequest(this.request); } - @Test(expected = RequestRejectedException.class) - public void getFirewalledRequestWhenExceedsUpperboundAsciiThenException() { - this.request.setRequestURI("/\u007f"); + @Test + public void getFirewalledRequestWhenJapaneseCharacterThenNoException() { + this.request.setServletPath("/\u3042"); this.firewall.getFirewalledRequest(this.request); } - // --- from DefaultHttpFirewallTests --- + @Test + public void getFirewalledRequestWhenExceedsUpperboundAsciiThenException() { + this.request.setRequestURI("/\u007f"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsNullThenException() { + this.request.setRequestURI("/\0"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsEncodedNullThenException() { + this.request.setRequestURI("/something%00/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsLowercaseEncodedLineFeedThenException() { + this.request.setRequestURI("/something%0a/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsUppercaseEncodedLineFeedThenException() { + this.request.setRequestURI("/something%0A/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } @Test public void getFirewalledRequestWhenContainsLineFeedThenException() { @@ -394,6 +398,20 @@ public class StrictHttpFirewallTests { .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } + @Test + public void getFirewalledRequestWhenContainsLowercaseEncodedCarriageReturnThenException() { + this.request.setRequestURI("/something%0d/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsUppercaseEncodedCarriageReturnThenException() { + this.request.setRequestURI("/something%0D/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + @Test public void getFirewalledRequestWhenContainsCarriageReturnThenException() { this.request.setRequestURI("/something\r/"); @@ -408,29 +426,119 @@ public class StrictHttpFirewallTests { .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } + @Test + public void getFirewalledRequestWhenServletPathContainsLineSeparatorThenException() { + this.request.setServletPath("/something\u2028/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsParagraphSeparatorThenException() { + this.request.setServletPath("/something\u2029/"); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenContainsLowercaseEncodedLineFeedAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedLineFeed(true); + this.request.setRequestURI("/something%0a/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenContainsUppercaseEncodedLineFeedAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedLineFeed(true); + this.request.setRequestURI("/something%0A/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenContainsLineFeedAndAllowedThenException() { + this.firewall.setAllowUrlEncodedLineFeed(true); + this.request.setRequestURI("/something\n/"); + // Expected an error because the line feed is decoded in an encoded part of the + // URL + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsLineFeedAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedLineFeed(true); + this.request.setServletPath("/something\n/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenContainsLowercaseEncodedCarriageReturnAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedCarriageReturn(true); + this.request.setRequestURI("/something%0d/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenContainsUppercaseEncodedCarriageReturnAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedCarriageReturn(true); + this.request.setRequestURI("/something%0D/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenContainsCarriageReturnAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedCarriageReturn(true); + this.request.setRequestURI("/something\r/"); + // Expected an error because the carriage return is decoded in an encoded part of + // the URL + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsCarriageReturnAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedCarriageReturn(true); + this.request.setServletPath("/something\r/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsLineSeparatorAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedLineSeparator(true); + this.request.setServletPath("/something\u2028/"); + this.firewall.getFirewalledRequest(this.request); + } + + @Test + public void getFirewalledRequestWhenServletPathContainsParagraphSeparatorAndAllowedThenNoException() { + this.firewall.setAllowUrlEncodedParagraphSeparator(true); + this.request.setServletPath("/something\u2029/"); + this.firewall.getFirewalledRequest(this.request); + } + /** - * On WebSphere 8.5 a URL like /context-root/a/b;%2f1/c can bypass a rule on - * /a/b/c because the pathInfo is /a/b;/1/c which ends up being /a/b/1/c - * while Spring MVC will strip the ; content from requestURI before the path - * is URL decoded. + * On WebSphere 8.5 a URL like /context-root/a/b;%2f1/c can bypass a rule on /a/b/c + * because the pathInfo is /a/b;/1/c which ends up being /a/b/1/c while Spring MVC + * will strip the ; content from requestURI before the path is URL decoded. */ - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenLowercaseEncodedPathThenException() { this.request.setRequestURI("/context-root/a/b;%2f1/c"); this.request.setContextPath("/context-root"); this.request.setServletPath(""); this.request.setPathInfo("/a/b;/1/c"); // URL decoded requestURI - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } - @Test(expected = RequestRejectedException.class) + @Test public void getFirewalledRequestWhenUppercaseEncodedPathThenException() { this.request.setRequestURI("/context-root/a/b;%2F1/c"); this.request.setContextPath("/context-root"); this.request.setServletPath(""); this.request.setPathInfo("/a/b;/1/c"); // URL decoded requestURI - - this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); } @Test @@ -442,7 +550,6 @@ public class StrictHttpFirewallTests { request.setContextPath("/context-root"); request.setServletPath(""); request.setPathInfo("/a/b;/1/c"); // URL decoded requestURI - this.firewall.getFirewalledRequest(request); } @@ -455,7 +562,6 @@ public class StrictHttpFirewallTests { request.setContextPath("/context-root"); request.setServletPath(""); request.setPathInfo("/a/b;/1/c"); // URL decoded requestURI - this.firewall.getFirewalledRequest(request); } @@ -468,7 +574,7 @@ public class StrictHttpFirewallTests { request.setContextPath("/context-root"); request.setServletPath(""); request.setPathInfo("/a/b//c"); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -480,7 +586,7 @@ public class StrictHttpFirewallTests { request.setContextPath("/context-root"); request.setServletPath(""); request.setPathInfo("/a/b//c"); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -492,7 +598,7 @@ public class StrictHttpFirewallTests { request.setContextPath("/context-root"); request.setServletPath(""); request.setPathInfo("/a/b//c"); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -504,7 +610,7 @@ public class StrictHttpFirewallTests { request.setContextPath("/context-root"); request.setServletPath(""); request.setPathInfo("/a/b//c"); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -513,7 +619,7 @@ public class StrictHttpFirewallTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); request.setRequestURI("/context-root/a/b%2F%2Fc"); this.firewall.getEncodedUrlBlacklist().removeAll(Arrays.asList("%2F%2F")); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -522,7 +628,7 @@ public class StrictHttpFirewallTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); request.setRequestURI("/context-root/a/b%2f%2fc"); this.firewall.getEncodedUrlBlacklist().removeAll(Arrays.asList("%2f%2f")); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -531,7 +637,7 @@ public class StrictHttpFirewallTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); request.setRequestURI("/context-root/a/b%2f%2Fc"); this.firewall.getEncodedUrlBlacklist().removeAll(Arrays.asList("%2f%2F")); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -540,7 +646,7 @@ public class StrictHttpFirewallTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); request.setRequestURI("/context-root/a/b%2F%2fc"); this.firewall.getEncodedUrlBlacklist().removeAll(Arrays.asList("%2F%2f")); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); } @Test @@ -548,22 +654,234 @@ public class StrictHttpFirewallTests { MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); request.setPathInfo("/a/b//c"); this.firewall.getDecodedUrlBlacklist().removeAll(Arrays.asList("//")); - assertThatCode(() -> this.firewall.getFirewalledRequest(request)).doesNotThrowAnyException(); + this.firewall.getFirewalledRequest(request); + } + + // blocklist + @Test + public void getFirewalledRequestWhenRemoveFromUpperCaseEncodedUrlBlocklistThenNoException() { + this.firewall.setAllowUrlEncodedSlash(true); + MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); + request.setRequestURI("/context-root/a/b%2F%2Fc"); + this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F%2F")); + this.firewall.getFirewalledRequest(request); + } + + @Test + public void getFirewalledRequestWhenRemoveFromLowerCaseEncodedUrlBlocklistThenNoException() { + this.firewall.setAllowUrlEncodedSlash(true); + MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); + request.setRequestURI("/context-root/a/b%2f%2fc"); + this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2f%2f")); + this.firewall.getFirewalledRequest(request); + } + + @Test + public void getFirewalledRequestWhenRemoveFromLowerCaseAndUpperCaseEncodedUrlBlocklistThenNoException() { + this.firewall.setAllowUrlEncodedSlash(true); + MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); + request.setRequestURI("/context-root/a/b%2f%2Fc"); + this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2f%2F")); + this.firewall.getFirewalledRequest(request); + } + + @Test + public void getFirewalledRequestWhenRemoveFromUpperCaseAndLowerCaseEncodedUrlBlocklistThenNoException() { + this.firewall.setAllowUrlEncodedSlash(true); + MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); + request.setRequestURI("/context-root/a/b%2F%2fc"); + this.firewall.getEncodedUrlBlocklist().removeAll(Arrays.asList("%2F%2f")); + this.firewall.getFirewalledRequest(request); + } + + @Test + public void getFirewalledRequestWhenRemoveFromDecodedUrlBlocklistThenNoException() { + MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); + request.setPathInfo("/a/b//c"); + this.firewall.getDecodedUrlBlocklist().removeAll(Arrays.asList("//")); + this.firewall.getFirewalledRequest(request); } @Test public void getFirewalledRequestWhenTrustedDomainThenNoException() { this.request.addHeader("Host", "example.org"); - this.firewall.setAllowedHostnames(hostname -> hostname.equals("example.org")); - - assertThatCode(() -> this.firewall.getFirewalledRequest(this.request)).doesNotThrowAnyException(); - } - - @Test(expected = RequestRejectedException.class) - public void getFirewalledRequestWhenUntrustedDomainThenException() { - this.request.addHeader("Host", "example.org"); - this.firewall.setAllowedHostnames(hostname -> hostname.equals("myexample.org")); - + this.firewall.setAllowedHostnames((hostname) -> hostname.equals("example.org")); this.firewall.getFirewalledRequest(this.request); } + + @Test + public void getFirewalledRequestWhenUntrustedDomainThenException() { + this.request.addHeader("Host", "example.org"); + this.firewall.setAllowedHostnames((hostname) -> hostname.equals("myexample.org")); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> this.firewall.getFirewalledRequest(this.request)); + } + + @Test + public void getFirewalledRequestGetHeaderWhenNotAllowedHeaderNameThenException() { + this.firewall.setAllowedHeaderNames((name) -> !name.equals("bad name")); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("bad name")); + } + + @Test + public void getFirewalledRequestGetHeaderWhenNotAllowedHeaderValueThenException() { + this.request.addHeader("good name", "bad value"); + this.firewall.setAllowedHeaderValues((value) -> !value.equals("bad value")); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("good name")); + } + + @Test + public void getFirewalledRequestGetDateHeaderWhenControlCharacterInHeaderNameThenException() { + this.request.addHeader("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getDateHeader("Bad\0Name")); + } + + @Test + public void getFirewalledRequestGetIntHeaderWhenControlCharacterInHeaderNameThenException() { + this.request.addHeader("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getIntHeader("Bad\0Name")); + } + + @Test + public void getFirewalledRequestGetHeaderWhenControlCharacterInHeaderNameThenException() { + this.request.addHeader("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("Bad\0Name")); + } + + @Test + public void getFirewalledRequestGetHeaderWhenUndefinedCharacterInHeaderNameThenException() { + this.request.addHeader("Bad\uFFFEName", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("Bad\uFFFEName")); + } + + @Test + public void getFirewalledRequestGetHeadersWhenControlCharacterInHeaderNameThenException() { + this.request.addHeader("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeaders("Bad\0Name")); + } + + @Test + public void getFirewalledRequestGetHeaderNamesWhenControlCharacterInHeaderNameThenException() { + this.request.addHeader("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> request.getHeaderNames().nextElement()); + } + + @Test + public void getFirewalledRequestGetHeaderWhenControlCharacterInHeaderValueThenException() { + this.request.addHeader("Something", "bad\0value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("Something")); + } + + @Test + public void getFirewalledRequestGetHeaderWhenUndefinedCharacterInHeaderValueThenException() { + this.request.addHeader("Something", "bad\uFFFEvalue"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getHeader("Something")); + } + + @Test + public void getFirewalledRequestGetHeadersWhenControlCharacterInHeaderValueThenException() { + this.request.addHeader("Something", "bad\0value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> request.getHeaders("Something").nextElement()); + } + + @Test + public void getFirewalledRequestGetParameterWhenControlCharacterInParameterNameThenException() { + this.request.addParameter("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(() -> request.getParameter("Bad\0Name")); + } + + @Test + public void getFirewalledRequestGetParameterMapWhenControlCharacterInParameterNameThenException() { + this.request.addParameter("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(request::getParameterMap); + } + + @Test + public void getFirewalledRequestGetParameterNamesWhenControlCharacterInParameterNameThenException() { + this.request.addParameter("Bad\0Name", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(request.getParameterNames()::nextElement); + } + + @Test + public void getFirewalledRequestGetParameterNamesWhenUndefinedCharacterInParameterNameThenException() { + this.request.addParameter("Bad\uFFFEName", "some value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class).isThrownBy(request.getParameterNames()::nextElement); + } + + @Test + public void getFirewalledRequestGetParameterValuesWhenNotAllowedInParameterValueThenException() { + this.firewall.setAllowedParameterValues((value) -> !value.equals("bad value")); + this.request.addParameter("Something", "bad value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> request.getParameterValues("Something")); + } + + @Test + public void getFirewalledRequestGetParameterValuesWhenNotAllowedInParameterNameThenException() { + this.firewall.setAllowedParameterNames((value) -> !value.equals("bad name")); + this.request.addParameter("bad name", "good value"); + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(RequestRejectedException.class) + .isThrownBy(() -> request.getParameterValues("bad name")); + } + + // gh-9598 + @Test + public void getFirewalledRequestGetParameterWhenNameIsNullThenIllegalArgumentException() { + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> request.getParameter(null)); + } + + // gh-9598 + @Test + public void getFirewalledRequestGetParameterValuesWhenNameIsNullThenIllegalArgumentException() { + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> request.getParameterValues(null)); + } + + // gh-9598 + @Test + public void getFirewalledRequestGetHeaderWhenNameIsNullThenNull() { + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThat(request.getHeader(null)).isNull(); + } + + // gh-9598 + @Test + public void getFirewalledRequestGetHeadersWhenNameIsNullThenEmptyEnumeration() { + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThat(request.getHeaders(null).hasMoreElements()).isFalse(); + } + + // gh-9598 + @Test + public void getFirewalledRequestGetIntHeaderWhenNameIsNullThenNegativeOne() { + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThat(request.getIntHeader(null)).isEqualTo(-1); + } + + @Test + public void getFirewalledRequestGetDateHeaderWhenNameIsNullThenNegativeOne() { + HttpServletRequest request = this.firewall.getFirewalledRequest(this.request); + assertThat(request.getDateHeader(null)).isEqualTo(-1); + } + }