PAR uses requested scopes on consent
Issue https://github.com/spring-projects/spring-authorization-server/pull/2182
This commit is contained in:
+114
-2
@@ -1071,6 +1071,94 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
.isEqualTo(true);
|
.isEqualTo(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gh-2182
|
||||||
|
@Test
|
||||||
|
public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
|
||||||
|
scopes.clear();
|
||||||
|
scopes.add("message.read");
|
||||||
|
scopes.add("message.write");
|
||||||
|
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
|
||||||
|
this.registeredClientRepository.save(registeredClient);
|
||||||
|
|
||||||
|
MvcResult mvcResult = this.mvc
|
||||||
|
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
|
||||||
|
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||||
|
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$.request_uri").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
|
||||||
|
|
||||||
|
String consentPage = this.mvc
|
||||||
|
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||||
|
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||||
|
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
|
||||||
|
.with(user("user")))
|
||||||
|
.andExpect(status().is2xxSuccessful())
|
||||||
|
.andReturn()
|
||||||
|
.getResponse()
|
||||||
|
.getContentAsString();
|
||||||
|
|
||||||
|
assertThat(consentPage).contains("Consent required");
|
||||||
|
assertThat(consentPage).contains(scopeCheckbox("message.read"));
|
||||||
|
assertThat(consentPage).contains(scopeCheckbox("message.write"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-2182
|
||||||
|
@Test
|
||||||
|
public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class)
|
||||||
|
.autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> {
|
||||||
|
scopes.clear();
|
||||||
|
scopes.add("message.read");
|
||||||
|
scopes.add("message.write");
|
||||||
|
}).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build();
|
||||||
|
this.registeredClientRepository.save(registeredClient);
|
||||||
|
|
||||||
|
MvcResult mvcResult = this.mvc
|
||||||
|
.perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient))
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)))
|
||||||
|
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||||
|
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||||
|
.andExpect(status().isCreated())
|
||||||
|
.andExpect(jsonPath("$.request_uri").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||||
|
.andReturn();
|
||||||
|
|
||||||
|
String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri");
|
||||||
|
|
||||||
|
mvcResult = this.mvc
|
||||||
|
.perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||||
|
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||||
|
.queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri)
|
||||||
|
.with(user("user")))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andReturn();
|
||||||
|
String redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||||
|
assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+");
|
||||||
|
|
||||||
|
String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name());
|
||||||
|
UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build();
|
||||||
|
MultiValueMap<String, String> redirectQueryParams = uriComponents.getQueryParams();
|
||||||
|
|
||||||
|
assertThat(uriComponents.getPath()).isEqualTo(consentPage);
|
||||||
|
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write");
|
||||||
|
assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID))
|
||||||
|
.isEqualTo(registeredClient.getClientId());
|
||||||
|
|
||||||
|
String state = extractParameterFromRedirectUri(redirectedUrl, "state");
|
||||||
|
OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE);
|
||||||
|
assertThat(authorization).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) {
|
private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) {
|
||||||
Map<String, Object> additionalParameters = new HashMap<>();
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
|
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE);
|
||||||
@@ -1125,8 +1213,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
|
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
|
||||||
String clientId = registeredClient.getClientId();
|
String clientId = registeredClient.getClientId();
|
||||||
String clientSecret = registeredClient.getClientSecret();
|
String clientSecret = registeredClient.getClientSecret();
|
||||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
|
||||||
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name());
|
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);
|
||||||
String credentialsString = clientId + ":" + clientSecret;
|
String credentialsString = clientId + ":" + clientSecret;
|
||||||
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||||
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
|
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
|
||||||
@@ -1496,4 +1584,28 @@ public class OAuth2AuthorizationCodeGrantTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EnableWebSecurity
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage
|
||||||
|
extends AuthorizationServerConfiguration {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.oauth2AuthorizationServer((authorizationServer) ->
|
||||||
|
authorizationServer
|
||||||
|
.pushedAuthorizationRequestEndpoint(Customizer.withDefaults())
|
||||||
|
.authorizationEndpoint((authorizationEndpoint) ->
|
||||||
|
authorizationEndpoint.consentPage(consentPage))
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests((authorize) ->
|
||||||
|
authorize.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-1
@@ -21,7 +21,9 @@ import java.time.Instant;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
@@ -283,8 +285,13 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
|||||||
Set<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)
|
Set<String> currentAuthorizedScopes = (currentAuthorizationConsent != null)
|
||||||
? currentAuthorizationConsent.getScopes() : null;
|
? currentAuthorizationConsent.getScopes() : null;
|
||||||
|
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
if (pushedAuthorization != null) {
|
||||||
|
additionalParameters.put(OAuth2ParameterNames.SCOPE, authorizationRequest.getScopes());
|
||||||
|
}
|
||||||
|
|
||||||
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
|
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
|
||||||
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
|
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, additionalParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationCodeRequestAuthentication,
|
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(authorizationCodeRequestAuthentication,
|
||||||
|
|||||||
+11
-1
@@ -291,10 +291,20 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
|||||||
|
|
||||||
String clientId = authorizationConsentAuthentication.getClientId();
|
String clientId = authorizationConsentAuthentication.getClientId();
|
||||||
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
|
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
|
||||||
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
|
|
||||||
Set<String> authorizedScopes = authorizationConsentAuthentication.getScopes();
|
Set<String> authorizedScopes = authorizationConsentAuthentication.getScopes();
|
||||||
String state = authorizationConsentAuthentication.getState();
|
String state = authorizationConsentAuthentication.getState();
|
||||||
|
|
||||||
|
Set<String> requestedScopes;
|
||||||
|
String requestUri = (String) authorizationCodeRequestAuthentication.getAdditionalParameters()
|
||||||
|
.get(OAuth2ParameterNames.REQUEST_URI);
|
||||||
|
if (StringUtils.hasText(requestUri)) {
|
||||||
|
requestedScopes = (Set<String>) authorizationConsentAuthentication.getAdditionalParameters()
|
||||||
|
.get(OAuth2ParameterNames.SCOPE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
requestedScopes = authorizationCodeRequestAuthentication.getScopes();
|
||||||
|
}
|
||||||
|
|
||||||
if (hasConsentUri()) {
|
if (hasConsentUri()) {
|
||||||
String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request))
|
String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request))
|
||||||
.queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes))
|
.queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", requestedScopes))
|
||||||
|
|||||||
Reference in New Issue
Block a user