From 8c297b125245e178696e4649e2b577caac932f61 Mon Sep 17 00:00:00 2001 From: Selene Feigl Date: Wed, 25 Sep 2024 12:56:21 +0200 Subject: [PATCH 1/2] Fix verification_uri in device authorization response when context path exists Closes gh-1714 --- ...uth2DeviceAuthorizationEndpointFilter.java | 18 ++++++++-- ...eviceAuthorizationEndpointFilterTests.java | 33 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java index ae219070..27dd4f6e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilter.java @@ -46,6 +46,7 @@ import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.security.web.util.RedirectUrlBuilder; import org.springframework.security.web.util.UrlUtils; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -220,9 +221,7 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode(); // Generate the fully-qualified verification URI - UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder - .fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) - .replacePath(this.verificationUri); + UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(resolveVerificationUri(request)); String verificationUri = uriComponentsBuilder.build().toUriString(); // @formatter:off String verificationUriComplete = uriComponentsBuilder @@ -242,4 +241,17 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques this.deviceAuthorizationHttpResponseConverter.write(deviceAuthorizationResponse, null, httpResponse); } + private String resolveVerificationUri(HttpServletRequest request) { + if (UrlUtils.isAbsoluteUrl(this.verificationUri)) { + return this.verificationUri; + } + RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); + urlBuilder.setScheme(request.getScheme()); + urlBuilder.setServerName(request.getServerName()); + urlBuilder.setPort(request.getServerPort()); + urlBuilder.setContextPath(request.getContextPath()); + urlBuilder.setPathInfo(this.verificationUri); + return urlBuilder.getUrl(); + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java index 9b9e18ed..8b09ec16 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java @@ -241,6 +241,39 @@ public class OAuth2DeviceAuthorizationEndpointFilterTests { assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); } + @Test + public void doFilterWhenDeviceAuthorizationRequestWithContextPathThenDeviceAuthorizationResponse() throws Exception { + Authentication authenticationResult = createAuthentication(); + given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); + + Authentication clientPrincipal = (Authentication) authenticationResult.getPrincipal(); + mockSecurityContext(clientPrincipal); + + MockHttpServletRequest request = createRequest(); + request.setContextPath("/contextPath"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + this.filter.doFilter(request, response, filterChain); + assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); + + ArgumentCaptor deviceAuthorizationRequestAuthenticationCaptor = ArgumentCaptor + .forClass(OAuth2DeviceAuthorizationRequestAuthenticationToken.class); + verify(this.authenticationManager).authenticate(deviceAuthorizationRequestAuthenticationCaptor.capture()); + verifyNoInteractions(filterChain); + + OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = readDeviceAuthorizationResponse(response); + String verificationUri = ISSUER_URI + "/contextPath" + VERIFICATION_URI; + assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo(verificationUri); + assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) + .isEqualTo("%s?%s=%s".formatted(verificationUri, OAuth2ParameterNames.USER_CODE, USER_CODE)); + OAuth2DeviceCode deviceCode = deviceAuthorizationResponse.getDeviceCode(); + assertThat(deviceCode.getTokenValue()).isEqualTo(DEVICE_CODE); + assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); + OAuth2UserCode userCode = deviceAuthorizationResponse.getUserCode(); + assertThat(userCode.getTokenValue()).isEqualTo(USER_CODE); + assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); + } + @Test public void doFilterWhenInvalidRequestErrorThenBadRequest() throws Exception { AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); From 68300d4cf9b45e5d0d270df675c5a565b243ac6e Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Fri, 4 Oct 2024 05:26:42 -0400 Subject: [PATCH 2/2] Polish gh-1729 --- ...h2DeviceAuthorizationEndpointFilterTests.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java index 8b09ec16..38f82ecc 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2DeviceAuthorizationEndpointFilterTests.java @@ -241,8 +241,10 @@ public class OAuth2DeviceAuthorizationEndpointFilterTests { assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); } + // gh-1714 @Test - public void doFilterWhenDeviceAuthorizationRequestWithContextPathThenDeviceAuthorizationResponse() throws Exception { + public void doFilterWhenDeviceAuthorizationRequestWithContextPathThenVerificationUriIncludesContextPath() + throws Exception { Authentication authenticationResult = createAuthentication(); given(this.authenticationManager.authenticate(any(Authentication.class))).willReturn(authenticationResult); @@ -256,22 +258,14 @@ public class OAuth2DeviceAuthorizationEndpointFilterTests { this.filter.doFilter(request, response, filterChain); assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value()); - ArgumentCaptor deviceAuthorizationRequestAuthenticationCaptor = ArgumentCaptor - .forClass(OAuth2DeviceAuthorizationRequestAuthenticationToken.class); - verify(this.authenticationManager).authenticate(deviceAuthorizationRequestAuthenticationCaptor.capture()); + verify(this.authenticationManager).authenticate(any(OAuth2DeviceAuthorizationRequestAuthenticationToken.class)); verifyNoInteractions(filterChain); OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = readDeviceAuthorizationResponse(response); String verificationUri = ISSUER_URI + "/contextPath" + VERIFICATION_URI; assertThat(deviceAuthorizationResponse.getVerificationUri()).isEqualTo(verificationUri); assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) - .isEqualTo("%s?%s=%s".formatted(verificationUri, OAuth2ParameterNames.USER_CODE, USER_CODE)); - OAuth2DeviceCode deviceCode = deviceAuthorizationResponse.getDeviceCode(); - assertThat(deviceCode.getTokenValue()).isEqualTo(DEVICE_CODE); - assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); - OAuth2UserCode userCode = deviceAuthorizationResponse.getUserCode(); - assertThat(userCode.getTokenValue()).isEqualTo(USER_CODE); - assertThat(deviceCode.getExpiresAt()).isAfter(deviceCode.getIssuedAt()); + .isEqualTo("%s?%s=%s".formatted(verificationUri, OAuth2ParameterNames.USER_CODE, USER_CODE)); } @Test