From 9767d1eabb4fa1486669d2f5e87a31059e955dc7 Mon Sep 17 00:00:00 2001 From: topiam Date: Sun, 18 Dec 2022 14:51:13 +0800 Subject: [PATCH 1/3] Fix redirect_uri resolver Closes gh-1012 --- ...th2AuthorizationCodeRequestAuthenticationProvider.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java index 1c3a1666..c93fa157 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java @@ -333,7 +333,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) { - String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient); + String redirectUri = resolveRedirectUri(authorizationCodeRequestAuthentication, authorizationRequest, registeredClient); if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) && (parameterName.equals(OAuth2ParameterNames.CLIENT_ID) || parameterName.equals(OAuth2ParameterNames.STATE))) { @@ -350,7 +350,11 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult); } - private static String resolveRedirectUri(OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) { + private static String resolveRedirectUri(OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, + OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) { + if (authorizationCodeRequestAuthentication!=null && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())){ + return authorizationCodeRequestAuthentication.getRedirectUri(); + } if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) { return authorizationRequest.getRedirectUri(); } From 1783bf761186545e200e782318eaa52f750295a3 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 15 Feb 2023 07:26:32 -0500 Subject: [PATCH 2/3] Polish gh-1013 --- ...tionCodeRequestAuthenticationProvider.java | 8 ++-- ...odeRequestAuthenticationProviderTests.java | 41 ++++++++++++------- .../client/TestRegisteredClients.java | 6 ++- .../OAuth2AuthorizationCodeGrantTests.java | 18 ++++---- .../annotation/web/configurers/OidcTests.java | 8 ++-- ...Auth2AuthorizationEndpointFilterTests.java | 12 ++++-- 6 files changed, 60 insertions(+), 33 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java index c93fa157..aecc5b38 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -350,9 +350,11 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult); } - private static String resolveRedirectUri(OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, + private static String resolveRedirectUri( + OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication, OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) { - if (authorizationCodeRequestAuthentication!=null && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())){ + + if (authorizationCodeRequestAuthentication != null && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) { return authorizationCodeRequestAuthentication.getRedirectUri(); } if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java index f0691cc6..f2e9eb7e 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeRequestAuthenticationProviderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -132,10 +132,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { @Test public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) .satisfies(ex -> @@ -301,10 +302,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { .build(); when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) .satisfies(ex -> @@ -319,10 +321,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { .build(); when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, + redirectUri, STATE, Collections.singleton("invalid-scope"), null); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) @@ -339,10 +342,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { .build(); when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) .satisfies(ex -> @@ -356,13 +360,14 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "unsupported"); OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters); + redirectUri, STATE, registeredClient.getScopes(), additionalParameters); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) .satisfies(ex -> @@ -377,12 +382,13 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters); + redirectUri, STATE, registeredClient.getScopes(), additionalParameters); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) .satisfies(ex -> @@ -398,10 +404,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { .thenReturn(registeredClient); this.principal.setAuthenticated(false); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -418,10 +425,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); OAuth2AuthorizationConsentAuthenticationToken authenticationResult = (OAuth2AuthorizationConsentAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -468,10 +476,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -494,10 +503,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { when(this.authorizationConsentService.findById(eq(registeredClient.getId()), eq(this.principal.getName()))) .thenReturn(previousAuthorizationConsent); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -511,13 +521,14 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[0]; Map additionalParameters = new HashMap<>(); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge"); additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters); + redirectUri, STATE, registeredClient.getScopes(), additionalParameters); OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -535,10 +546,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { OAuth2TokenGenerator authorizationCodeGenerator = mock(OAuth2TokenGenerator.class); this.authenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class) @@ -559,10 +571,11 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests { Consumer authenticationValidator = mock(Consumer.class); this.authenticationProvider.setAuthenticationValidator(authenticationValidator); + String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[2]; OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken( AUTHORIZATION_URI, registeredClient.getClientId(), principal, - registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null); + redirectUri, STATE, registeredClient.getScopes(), null); OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java index bf2c2640..6036f0ba 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/client/TestRegisteredClients.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -35,7 +35,9 @@ public class TestRegisteredClients { .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .redirectUri("https://example.com") + .redirectUri("https://example.com/callback-1") + .redirectUri("https://example.com/callback-2") + .redirectUri("https://example.com/callback-3") .scope("scope1"); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java index ab57c54b..c794097f 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -289,13 +289,15 @@ public class OAuth2AuthorizationCodeGrantTests { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); + MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient); MvcResult mvcResult = this.mvc.perform(get(authorizationEndpointUri) - .params(getAuthorizationRequestParameters(registeredClient)) + .params(authorizationRequestParameters) .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); + String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); + assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); @@ -423,15 +425,17 @@ public class OAuth2AuthorizationCodeGrantTests { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); + MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient); MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .params(getAuthorizationRequestParameters(registeredClient)) + .params(authorizationRequestParameters) .param(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE) .param(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256") .with(user("user"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); + String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); + assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); @@ -527,7 +531,7 @@ public class OAuth2AuthorizationCodeGrantTests { .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); + assertThat(redirectedUrl).matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); @@ -614,7 +618,7 @@ public class OAuth2AuthorizationCodeGrantTests { .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); + assertThat(redirectedUrl).matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java index fc7c363a..d8335308 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -182,13 +182,15 @@ public class OidcTests { RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); this.registeredClientRepository.save(registeredClient); + MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient); MvcResult mvcResult = this.mvc.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .params(getAuthorizationRequestParameters(registeredClient)) + .params(authorizationRequestParameters) .with(user("user").roles("A", "B"))) .andExpect(status().is3xxRedirection()) .andReturn(); String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=state"); + String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); + assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java index 386f1072..ae76f3a1 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 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. @@ -299,7 +299,9 @@ public class OAuth2AuthorizationEndpointFilterTests { verifyNoInteractions(filterChain); assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?error=errorCode&error_description=errorDescription&error_uri=errorUri&state=state"); + assertThat(response.getRedirectedUrl()).isEqualTo( + request.getParameter(OAuth2ParameterNames.REDIRECT_URI) + + "?error=errorCode&error_description=errorDescription&error_uri=errorUri&state=state"); assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.principal); } @@ -560,7 +562,8 @@ public class OAuth2AuthorizationEndpointFilterTests { .extracting(WebAuthenticationDetails::getRemoteAddress) .isEqualTo(REMOTE_ADDRESS); assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?code=code&state=state"); + assertThat(response.getRedirectedUrl()).isEqualTo( + request.getParameter(OAuth2ParameterNames.REDIRECT_URI) + "?code=code&state=state"); } @Test @@ -591,7 +594,8 @@ public class OAuth2AuthorizationEndpointFilterTests { verifyNoInteractions(filterChain); assertThat(response.getStatus()).isEqualTo(HttpStatus.FOUND.value()); - assertThat(response.getRedirectedUrl()).isEqualTo("https://example.com?code=code&state=state"); + assertThat(response.getRedirectedUrl()).isEqualTo( + request.getParameter(OAuth2ParameterNames.REDIRECT_URI) + "?code=code&state=state"); } private void doFilterWhenAuthorizationRequestInvalidParameterThenError(RegisteredClient registeredClient, From 743dba7e5f00675d0aeac0eb515bda8e4b866ae5 Mon Sep 17 00:00:00 2001 From: Joe Grandja Date: Wed, 15 Feb 2023 10:39:15 -0500 Subject: [PATCH 3/3] Setup Forward Merge --- git/hooks/forward-merge | 135 ++++++++++++++++++++++++++++++++ git/hooks/prepare-forward-merge | 71 +++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100755 git/hooks/forward-merge create mode 100755 git/hooks/prepare-forward-merge diff --git a/git/hooks/forward-merge b/git/hooks/forward-merge new file mode 100755 index 00000000..51640718 --- /dev/null +++ b/git/hooks/forward-merge @@ -0,0 +1,135 @@ +#!/usr/bin/ruby +require 'json' +require 'net/http' +require 'yaml' +require 'logger' + +$log = Logger.new(STDOUT) +$log.level = Logger::WARN + +class ForwardMerge + attr_reader :issue, :milestone, :message, :line + def initialize(issue, milestone, message, line) + @issue = issue + @milestone = milestone + @message = message + @line = line + end +end + +def find_forward_merges(message_file) + $log.debug "Searching for forward merge" + rev=`git rev-parse -q --verify MERGE_HEAD`.strip + $log.debug "Found #{rev} from git rev-parse" + return nil unless rev + message = File.read(message_file) + forward_merges = [] + message.each_line do |line| + $log.debug "Checking #{line} for message" + match = /^(?:Fixes|Closes) gh-(\d+) in (\d\.\d\.[\dx](?:[\.\-](?:M|RC)\d)?)$/.match(line) + if match then + issue = match[1] + milestone = match[2] + $log.debug "Matched reference to issue #{issue} in milestone #{milestone}" + forward_merges << ForwardMerge.new(issue, milestone, message, line) + end + end + $log.debug "No match in merge message" unless forward_merges + return forward_merges +end + +def get_issue(username, password, repository, number) + $log.debug "Getting issue #{number} from GitHub repository #{repository}" + uri = URI("https://api.github.com/repos/#{repository}/issues/#{number}") + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl=true + request = Net::HTTP::Get.new(uri.path) + request.basic_auth(username, password) + response = http.request(request) + $log.debug "Get HTTP response #{response.code}" + return JSON.parse(response.body) unless response.code != '200' + puts "Failed to retrieve issue #{number}: #{response.message}" + exit 1 +end + +def find_milestone(username, password, repository, title) + $log.debug "Finding milestone #{title} from GitHub repository #{repository}" + uri = URI("https://api.github.com/repos/#{repository}/milestones") + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl=true + request = Net::HTTP::Get.new(uri.path) + request.basic_auth(username, password) + response = http.request(request) + milestones = JSON.parse(response.body) + if title.end_with?(".x") + prefix = title.delete_suffix('.x') + $log.debug "Finding nearest milestone from candidates starting with #{prefix}" + titles = milestones.map { |milestone| milestone['title'] } + titles = titles.select{ |title| title.start_with?(prefix) unless title.end_with?('.x')} + titles = titles.sort_by { |v| Gem::Version.new(v) } + $log.debug "Considering candidates #{titles}" + if(titles.empty?) + puts "Cannot find nearest milestone for prefix #{title}" + exit 1 + end + title = titles.first + $log.debug "Found nearest milestone #{title}" + end + milestones.each do |milestone| + $log.debug "Considering #{milestone['title']}" + return milestone['number'] if milestone['title'] == title + end + puts "Milestone #{title} not found in #{repository}" + exit 1 +end + +def create_issue(username, password, repository, original, title, labels, milestone, milestone_name, dry_run) + $log.debug "Finding forward-merge issue in GitHub repository #{repository} for '#{title}'" + uri = URI("https://api.github.com/repos/#{repository}/issues") + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl=true + request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json') + request.basic_auth(username, password) + request.body = { + title: title, + labels: labels, + milestone: milestone.to_i, + body: "Forward port of issue ##{original} to #{milestone_name}." + }.to_json + if dry_run then + puts "Dry run" + puts "POSTing to #{uri} with body #{request.body}" + return "dry-run" + end + response = JSON.parse(http.request(request).body) + $log.debug "Created new issue #{response['number']}" + return response['number'] +end + +$log.debug "Running forward-merge hook script" +message_file=ARGV[0] + +forward_merges = find_forward_merges(message_file) +exit 0 unless forward_merges + +$log.debug "Loading config from ~/.spring-authorization-server/forward_merge.yml" +config = YAML.load_file(File.join(Dir.home, '.spring-authorization-server', 'forward-merge.yml')) +username = config['github']['credentials']['username'] +password = config['github']['credentials']['password'] +dry_run = config['dry_run'] +repository = 'spring-projects/spring-authorization-server' + +forward_merges.each do |forward_merge| + existing_issue = get_issue(username, password, repository, forward_merge.issue) + title = existing_issue['title'] + labels = existing_issue['labels'].map { |label| label['name'] } + labels << "status: forward-port" + $log.debug "Processing issue '#{title}'" + + milestone = find_milestone(username, password, repository, forward_merge.milestone) + new_issue_number = create_issue(username, password, repository, forward_merge.issue, title, labels, milestone, forward_merge.milestone, dry_run) + + puts "Created gh-#{new_issue_number} for forward port of gh-#{forward_merge.issue} into #{forward_merge.milestone}" + rewritten_message = forward_merge.message.sub(forward_merge.line, "Closes gh-#{new_issue_number}\n") + File.write(message_file, rewritten_message) +end diff --git a/git/hooks/prepare-forward-merge b/git/hooks/prepare-forward-merge new file mode 100755 index 00000000..c32f023a --- /dev/null +++ b/git/hooks/prepare-forward-merge @@ -0,0 +1,71 @@ +#!/usr/bin/ruby +require 'json' +require 'net/http' +require 'yaml' +require 'logger' + +$main_branch = "1.0.x" + +$log = Logger.new(STDOUT) +$log.level = Logger::WARN + +def get_fixed_issues() + $log.debug "Searching for for forward merge" + rev=`git rev-parse -q --verify MERGE_HEAD`.strip + $log.debug "Found #{rev} from git rev-parse" + return nil unless rev + fixed = [] + message = `git log -1 --pretty=%B #{rev}` + message.each_line do |line| + $log.debug "Checking #{line} for message" + fixed << line.strip if /^(?:Fixes|Closes) gh-(\d+)/.match(line) + end + $log.debug "Found fixed issues #{fixed}" + return fixed; +end + +def rewrite_message(message_file, fixed) + current_branch = `git rev-parse --abbrev-ref HEAD`.strip + if current_branch == "main" + current_branch = $main_branch + end + rewritten_message = "" + message = File.read(message_file) + message.each_line do |line| + match = /^Merge.*branch\ '(.*)'(?:\ into\ (.*))?$/.match(line) + if match + from_branch = match[1] + if from_branch.include? "/" + from_branch = from_branch.partition("/").last + end + to_brach = match[2] + $log.debug "Rewriting merge message" + line = "Merge branch '#{from_branch}'" + (to_brach ? " into #{to_brach}\n" : "\n") + end + if fixed and line.start_with?("#") + $log.debug "Adding fixed" + rewritten_message << "\n" + fixed.each do |fixes| + rewritten_message << "#{fixes} in #{current_branch}\n" + end + fixed = nil + end + rewritten_message << line + end + return rewritten_message +end + +$log.debug "Running prepare-forward-merge hook script" + +message_file=ARGV[0] +message_type=ARGV[1] + +if message_type != "merge" + $log.debug "Not a merge commit" + exit 0; +end + +$log.debug "Searching for for forward merge" +fixed = get_fixed_issues() +rewritten_message = rewrite_message(message_file, fixed) +File.write(message_file, rewritten_message)