Polish gh-280
This commit is contained in:
@@ -35,12 +35,12 @@ import org.springframework.security.config.annotation.web.configurers.ExceptionH
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider;
|
||||
@@ -79,6 +79,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see AbstractHttpConfigurer
|
||||
* @see RegisteredClientRepository
|
||||
* @see OAuth2AuthorizationService
|
||||
* @see OAuth2AuthorizationConsentService
|
||||
* @see OAuth2AuthorizationEndpointFilter
|
||||
* @see OAuth2TokenEndpointFilter
|
||||
* @see OAuth2TokenIntrospectionEndpointFilter
|
||||
@@ -138,7 +139,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
/**
|
||||
* Sets the authorization consent service.
|
||||
*
|
||||
* @param authorizationConsentService the authorization service
|
||||
* @param authorizationConsentService the authorization consent service
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> authorizationConsentService(OAuth2AuthorizationConsentService authorizationConsentService) {
|
||||
@@ -160,17 +161,17 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the URL to redirect Resource Owners to if consent is required during
|
||||
* Specify the URI to redirect Resource Owners to if consent is required during
|
||||
* the {@code authorization_code} flow. A default consent page will be generated when
|
||||
* this attribute is not specified.
|
||||
*
|
||||
* If a URL is specified, users are required to process the specified URL to generate
|
||||
* If a URI is specified, applications are required to process the specified URI to generate
|
||||
* a consent page. The query string will contain the following parameters:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@code client_id} the client identifier</li>
|
||||
* <li>{@code scope} the space separated list of scopes present in the authorization request</li>
|
||||
* <li>{@code state} a CSRF protection token</li>
|
||||
* <li>{@code client_id} - the client identifier</li>
|
||||
* <li>{@code scope} - the space separated list of scopes present in the authorization request</li>
|
||||
* <li>{@code state} - a CSRF protection token</li>
|
||||
* </ul>
|
||||
*
|
||||
* In general, the consent page should create a form that submits
|
||||
@@ -181,14 +182,13 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
* <li>It must be submitted to {@link ProviderSettings#authorizationEndpoint()}</li>
|
||||
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
|
||||
* <li>It must include the received {@code state} as an HTTP parameter</li>
|
||||
* <li>It must include the list of {@code scope}s the {@code Resource Owners}
|
||||
* consents to as an HTTP parameter</li>
|
||||
* <li>It must include the {@code consent_action} parameter, with value either
|
||||
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
|
||||
* consented to as an HTTP parameter</li>
|
||||
* <li>It must include the {@code consent_action} parameter, with a value either
|
||||
* {@code approve} or {@code cancel} as an HTTP parameter</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param consentPage the consent page to redirect to if consent is required (e.g. "/consent")
|
||||
* @param consentPage the consent page to redirect to if consent is required (e.g. "/oauth2/consent")
|
||||
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
|
||||
*/
|
||||
public OAuth2AuthorizationServerConfigurer<B> consentPage(String consentPage) {
|
||||
@@ -316,9 +316,8 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
|
||||
getRegisteredClientRepository(builder),
|
||||
getAuthorizationService(builder),
|
||||
getAuthorizationConsentService(builder),
|
||||
providerSettings.authorizationEndpoint()
|
||||
);
|
||||
if (this.consentPage != null) {
|
||||
providerSettings.authorizationEndpoint());
|
||||
if (StringUtils.hasText(this.consentPage)) {
|
||||
authorizationEndpointFilter.setUserConsentUri(this.consentPage);
|
||||
}
|
||||
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -25,6 +22,9 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthorizationConsentService} that stores {@link OAuth2AuthorizationConsent}'s in-memory.
|
||||
*
|
||||
@@ -102,4 +102,5 @@ public final class InMemoryOAuth2AuthorizationConsentService implements OAuth2Au
|
||||
private static int getId(OAuth2AuthorizationConsent authorizationConsent) {
|
||||
return getId(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
@@ -23,16 +30,9 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A representation of an OAuth 2.0 "consent" to an Authorization request, which holds state related to the
|
||||
* set of {@link #getAuthorities()} authorities} granted to a {@link #getRegisteredClientId() client} by the
|
||||
* set of {@link #getAuthorities() authorities} granted to a {@link #getRegisteredClientId() client} by the
|
||||
* {@link #getPrincipalName() resource owner}.
|
||||
* <p>
|
||||
* When authorizing access for a given client, the resource owner may only grant a subset of the authorities
|
||||
@@ -130,7 +130,7 @@ public final class OAuth2AuthorizationConsent implements Serializable {
|
||||
/**
|
||||
* A builder for {@link OAuth2AuthorizationConsent}.
|
||||
*/
|
||||
public final static class Builder implements Serializable {
|
||||
public static final class Builder implements Serializable {
|
||||
private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
|
||||
|
||||
private final String registeredClientId;
|
||||
@@ -151,10 +151,10 @@ public final class OAuth2AuthorizationConsent implements Serializable {
|
||||
|
||||
/**
|
||||
* Adds a scope to the collection of {@code authorities} in the resulting {@link OAuth2AuthorizationConsent},
|
||||
* wrapping it in a SimpleGrantedAuthority, prefixed by {@code SCOPE_}. For example, a
|
||||
* wrapping it in a {@link SimpleGrantedAuthority}, prefixed by {@code SCOPE_}. For example, a
|
||||
* {@code message.write} scope would be stored as {@code SCOPE_message.write}.
|
||||
*
|
||||
* @param scope the {@code scope}
|
||||
* @param scope the scope
|
||||
* @return the {@code Builder} for further configuration
|
||||
*/
|
||||
public Builder scope(String scope) {
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.security.Principal;
|
||||
* @see OAuth2AuthorizationConsent
|
||||
*/
|
||||
public interface OAuth2AuthorizationConsentService {
|
||||
|
||||
/**
|
||||
* Saves the {@link OAuth2AuthorizationConsent}.
|
||||
*
|
||||
@@ -53,4 +54,5 @@ public interface OAuth2AuthorizationConsentService {
|
||||
*/
|
||||
@Nullable
|
||||
OAuth2AuthorizationConsent findById(String registeredClientId, String principalName);
|
||||
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
* @see OAuth2AuthorizationService
|
||||
* @see OAuth2AuthorizationConsentService
|
||||
* @see OAuth2Authorization
|
||||
* @see OAuth2AuthorizationConsent
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Request</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
|
||||
@@ -139,10 +140,7 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
@Deprecated
|
||||
public OAuth2AuthorizationEndpointFilter(RegisteredClientRepository registeredClientRepository,
|
||||
OAuth2AuthorizationService authorizationService, String authorizationEndpointUri) {
|
||||
this(registeredClientRepository,
|
||||
authorizationService,
|
||||
new InMemoryOAuth2AuthorizationConsentService(),
|
||||
authorizationEndpointUri);
|
||||
this(registeredClientRepository, authorizationService, new InMemoryOAuth2AuthorizationConsentService(), authorizationEndpointUri);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,19 +160,19 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
*
|
||||
* @param registeredClientRepository the repository of registered clients
|
||||
* @param authorizationService the authorization service
|
||||
* @param consentService the consent service
|
||||
* @param authorizationConsentService the authorization consent service
|
||||
* @param authorizationEndpointUri the endpoint {@code URI} for authorization requests
|
||||
*/
|
||||
public OAuth2AuthorizationEndpointFilter(RegisteredClientRepository registeredClientRepository,
|
||||
OAuth2AuthorizationService authorizationService, OAuth2AuthorizationConsentService consentService,
|
||||
OAuth2AuthorizationService authorizationService, OAuth2AuthorizationConsentService authorizationConsentService,
|
||||
String authorizationEndpointUri) {
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(consentService, "consentService cannot be null");
|
||||
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
|
||||
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
this.authorizationService = authorizationService;
|
||||
this.authorizationConsentService = consentService;
|
||||
this.authorizationConsentService = authorizationConsentService;
|
||||
|
||||
RequestMatcher authorizationRequestGetMatcher = new AntPathRequestMatcher(
|
||||
authorizationEndpointUri, HttpMethod.GET.name());
|
||||
@@ -196,14 +194,14 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the URL to redirect Resource Owners to if consent is required. A default consent
|
||||
* Specify the URI to redirect Resource Owners to if consent is required. A default consent
|
||||
* page will be generated when this attribute is not specified.
|
||||
*
|
||||
* @param customConsentUri the URI of the custom consent page to redirect to if consent is required (e.g. "/consent")
|
||||
* @param userConsentUri the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
|
||||
* @see org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer#consentPage(String)
|
||||
*/
|
||||
public final void setUserConsentUri(String customConsentUri) {
|
||||
this.userConsentUri = customConsentUri;
|
||||
public final void setUserConsentUri(String userConsentUri) {
|
||||
this.userConsentUri = userConsentUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,8 +257,9 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
.attribute(Principal.class.getName(), principal)
|
||||
.attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest);
|
||||
|
||||
OAuth2AuthorizationConsent previousConsent = this.authorizationConsentService.findById(registeredClient.getClientId(), principal.getName());
|
||||
if (requireUserConsent(registeredClient, authorizationRequest, previousConsent)) {
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
|
||||
registeredClient.getId(), principal.getName());
|
||||
if (requireUserConsent(registeredClient, authorizationRequest, currentAuthorizationConsent)) {
|
||||
String state = this.stateGenerator.generateKey();
|
||||
OAuth2Authorization authorization = builder
|
||||
.attribute(OAuth2ParameterNames.STATE, state)
|
||||
@@ -269,16 +268,16 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
|
||||
// TODO Need to remove 'in-flight' authorization if consent step is not completed (e.g. approved or cancelled)
|
||||
|
||||
if (this.hasCustomUserConsentPage()) {
|
||||
String redirect = UriComponentsBuilder
|
||||
if (hasCustomUserConsentPage()) {
|
||||
String redirectUri = UriComponentsBuilder
|
||||
.fromUriString(this.userConsentUri)
|
||||
.queryParam(OAuth2ParameterNames.SCOPE, String.join(" ", authorizationRequest.getScopes()))
|
||||
.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.queryParam(OAuth2ParameterNames.STATE, state)
|
||||
.toUriString();
|
||||
this.redirectStrategy.sendRedirect(request, response, redirect);
|
||||
this.redirectStrategy.sendRedirect(request, response, redirectUri);
|
||||
} else {
|
||||
UserConsentPage.displayConsent(request, response, registeredClient, authorization, previousConsent);
|
||||
UserConsentPage.displayConsent(request, response, registeredClient, authorization, currentAuthorizationConsent);
|
||||
}
|
||||
} else {
|
||||
Instant issuedAt = Instant.now();
|
||||
@@ -305,10 +304,12 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
private boolean hasCustomUserConsentPage() {
|
||||
return this.userConsentUri != null;
|
||||
return StringUtils.hasText(this.userConsentUri);
|
||||
}
|
||||
|
||||
private boolean requireUserConsent(RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest, OAuth2AuthorizationConsent previousConsent) {
|
||||
private static boolean requireUserConsent(RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest,
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent) {
|
||||
|
||||
if (!registeredClient.getClientSettings().requireUserConsent()) {
|
||||
return false;
|
||||
}
|
||||
@@ -318,8 +319,8 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (previousConsent != null &&
|
||||
previousConsent.getScopes().containsAll(authorizationRequest.getScopes())) {
|
||||
if (currentAuthorizationConsent != null &&
|
||||
currentAuthorizationConsent.getScopes().containsAll(authorizationRequest.getScopes())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -364,16 +365,18 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
authorizedScopes.add(OidcScopes.OPENID);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent previousConsent = this.authorizationConsentService.findById(
|
||||
userConsentRequestContext.getClientId(),
|
||||
userConsentRequestContext.getAuthorization().getPrincipalName()
|
||||
);
|
||||
for (String requestedScope : userConsentRequestContext.getAuthorizationRequest().getScopes()) {
|
||||
if (previousConsent != null && previousConsent.getScopes().contains(requestedScope)) {
|
||||
authorizedScopes.add(requestedScope);
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
|
||||
userConsentRequestContext.getAuthorization().getRegisteredClientId(),
|
||||
userConsentRequestContext.getAuthorization().getPrincipalName());
|
||||
if (currentAuthorizationConsent != null) {
|
||||
Set<String> currentAuthorizedScopes = currentAuthorizationConsent.getScopes();
|
||||
for (String requestedScope : userConsentRequestContext.getAuthorizationRequest().getScopes()) {
|
||||
if (currentAuthorizedScopes.contains(requestedScope)) {
|
||||
authorizedScopes.add(requestedScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
saveAuthorizationConsent(previousConsent, userConsentRequestContext);
|
||||
saveAuthorizationConsent(currentAuthorizationConsent, userConsentRequestContext);
|
||||
|
||||
OAuth2Authorization authorization = OAuth2Authorization.from(userConsentRequestContext.getAuthorization())
|
||||
.token(authorizationCode)
|
||||
@@ -388,26 +391,25 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
authorizationCode, userConsentRequestContext.getAuthorizationRequest().getState());
|
||||
}
|
||||
|
||||
private void saveAuthorizationConsent(OAuth2AuthorizationConsent previousConsent, UserConsentRequestContext userConsentRequestContext) {
|
||||
private void saveAuthorizationConsent(OAuth2AuthorizationConsent currentAuthorizationConsent, UserConsentRequestContext userConsentRequestContext) {
|
||||
if (CollectionUtils.isEmpty(userConsentRequestContext.getScopes())) {
|
||||
return;
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent.Builder userConsentBuilder;
|
||||
if (previousConsent == null) {
|
||||
userConsentBuilder = OAuth2AuthorizationConsent.withId(
|
||||
userConsentRequestContext.getClientId(),
|
||||
userConsentRequestContext.getAuthorization().getPrincipalName()
|
||||
);
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder;
|
||||
if (currentAuthorizationConsent == null) {
|
||||
authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(
|
||||
userConsentRequestContext.getAuthorization().getRegisteredClientId(),
|
||||
userConsentRequestContext.getAuthorization().getPrincipalName());
|
||||
} else {
|
||||
userConsentBuilder = OAuth2AuthorizationConsent.from(previousConsent);
|
||||
authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent);
|
||||
}
|
||||
|
||||
for (String authorizedScope : userConsentRequestContext.getScopes()) {
|
||||
userConsentBuilder.scope(authorizedScope);
|
||||
authorizationConsentBuilder.scope(authorizedScope);
|
||||
}
|
||||
OAuth2AuthorizationConsent userConsent = userConsentBuilder.build();
|
||||
this.authorizationConsentService.save(userConsent);
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
|
||||
this.authorizationConsentService.save(authorizationConsent);
|
||||
}
|
||||
|
||||
private void validateAuthorizationRequest(OAuth2AuthorizationRequestContext authorizationRequestContext) {
|
||||
@@ -815,9 +817,9 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
|
||||
private static void displayConsent(HttpServletRequest request, HttpServletResponse response,
|
||||
RegisteredClient registeredClient, OAuth2Authorization authorization,
|
||||
OAuth2AuthorizationConsent previousConsent) throws IOException {
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent) throws IOException {
|
||||
|
||||
String consentPage = generateConsentPage(request, registeredClient, authorization, previousConsent);
|
||||
String consentPage = generateConsentPage(request, registeredClient, authorization, currentAuthorizationConsent);
|
||||
response.setContentType(TEXT_HTML_UTF8.toString());
|
||||
response.setContentLength(consentPage.getBytes(StandardCharsets.UTF_8).length);
|
||||
response.getWriter().write(consentPage);
|
||||
@@ -832,17 +834,26 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
private static String generateConsentPage(HttpServletRequest request,
|
||||
RegisteredClient registeredClient, OAuth2Authorization authorization, OAuth2AuthorizationConsent previousConsent) {
|
||||
RegisteredClient registeredClient, OAuth2Authorization authorization,
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent) {
|
||||
|
||||
Set<String> currentAuthorizedScopes;
|
||||
if (currentAuthorizationConsent != null) {
|
||||
currentAuthorizedScopes = currentAuthorizationConsent.getScopes();
|
||||
} else {
|
||||
currentAuthorizedScopes = Collections.emptySet();
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
|
||||
Set<String> scopes = new HashSet<>();
|
||||
Set<String> previouslyApprovedScopes = new HashSet<>();
|
||||
Set<String> scopesToAuthorize = new HashSet<>();
|
||||
Set<String> scopesPreviouslyAuthorized = new HashSet<>();
|
||||
for (String scope : authorizationRequest.getScopes()) {
|
||||
if (previousConsent != null && previousConsent.getScopes().contains(scope)) {
|
||||
previouslyApprovedScopes.add(scope);
|
||||
if (currentAuthorizedScopes.contains(scope)) {
|
||||
scopesPreviouslyAuthorized.add(scope);
|
||||
} else if (!scope.equals(OidcScopes.OPENID)) { // openid scope does not require consent
|
||||
scopes.add(scope);
|
||||
scopesToAuthorize.add(scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,16 +890,16 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
||||
builder.append(" <input type=\"hidden\" name=\"client_id\" value=\"" + registeredClient.getClientId() + "\">");
|
||||
builder.append(" <input type=\"hidden\" name=\"state\" value=\"" + state + "\">");
|
||||
|
||||
for (String scope : scopes) {
|
||||
for (String scope : scopesToAuthorize) {
|
||||
builder.append(" <div class=\"form-group form-check py-1\">");
|
||||
builder.append(" <input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"" + scope + "\" id=\"" + scope + "\" checked>");
|
||||
builder.append(" <label class=\"form-check-label\" for=\"" + scope + "\">" + scope + "</label>");
|
||||
builder.append(" </div>");
|
||||
}
|
||||
|
||||
if (!previouslyApprovedScopes.isEmpty()) {
|
||||
if (!scopesPreviouslyAuthorized.isEmpty()) {
|
||||
builder.append(" <p>You have already granted the following permissions to the above app:</p>");
|
||||
for (String scope : previouslyApprovedScopes) {
|
||||
for (String scope : scopesPreviouslyAuthorized) {
|
||||
builder.append(" <div class=\"form-group form-check py-1\">");
|
||||
builder.append(" <input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" id=\"" + scope + "\" checked disabled>");
|
||||
builder.append(" <label class=\"form-check-label\" for=\"" + scope + "\">" + scope + "</label>");
|
||||
|
||||
@@ -122,7 +122,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
private static ProviderSettings providerSettings;
|
||||
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
private static String consentPage = "/custom-consent";
|
||||
private static String consentPage = "/oauth2/consent";
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@@ -360,15 +360,13 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
.getResponse()
|
||||
.getContentAsString();
|
||||
|
||||
|
||||
assertThat(consentPage).contains("Consent required");
|
||||
assertThat(consentPage).contains(scopeCheckbox("message.read"));
|
||||
assertThat(consentPage).contains(scopeCheckbox("message.write"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenConsentRequestReturnAccessTokenResponse() throws Exception {
|
||||
final String stateParameter = "consent-state";
|
||||
public void requestWhenConsentRequestThenReturnAccessTokenResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
@@ -381,14 +379,15 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
.build();
|
||||
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization stateTokenAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName("user")
|
||||
.build();
|
||||
|
||||
final String stateParameter = "state";
|
||||
|
||||
when(authorizationService.findByToken(
|
||||
eq(stateParameter),
|
||||
eq(new OAuth2TokenType(OAuth2ParameterNames.STATE))))
|
||||
.thenReturn(stateTokenAuthorization);
|
||||
eq(stateParameter), eq(new OAuth2TokenType(OAuth2ParameterNames.STATE))))
|
||||
.thenReturn(authorization);
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(post(OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
@@ -480,10 +479,10 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
|
||||
private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception {
|
||||
String clientId = registeredClient.getClientId();
|
||||
String secret = registeredClient.getClientSecret();
|
||||
String clientSecret = registeredClient.getClientSecret();
|
||||
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||
String credentialsString = clientId + ":" + secret;
|
||||
clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name());
|
||||
String credentialsString = clientId + ":" + clientSecret;
|
||||
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||
return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-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.server.authorization;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link InMemoryOAuth2AuthorizationConsentService}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class InMemoryOAuth2AuthorizationConsentServiceTest {
|
||||
private InMemoryOAuth2AuthorizationConsentService consentService;
|
||||
|
||||
private static final String CLIENT_ID = "client-id";
|
||||
private static final String PRINCIPAL_NAME = "principal-name";
|
||||
private static final OAuth2AuthorizationConsent CONSENT = OAuth2AuthorizationConsent
|
||||
.withId(CLIENT_ID, PRINCIPAL_NAME)
|
||||
.authority(new SimpleGrantedAuthority("some.authority"))
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.consentService = new InMemoryOAuth2AuthorizationConsentService();
|
||||
this.consentService.save(CONSENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorVaragsWhenAuthorizationConsentNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService((OAuth2AuthorizationConsent) null))
|
||||
.withMessage("authorizationConsent cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorListWhenAuthorizationConsentsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService((List<OAuth2AuthorizationConsent>) null))
|
||||
.withMessage("authorizationConsents cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenDuplicateAuthorizationConsentsThenThrowIllegalArgumentException() {
|
||||
OAuth2AuthorizationConsent authorizationConsent = OAuth2AuthorizationConsent.withId("client-id", "principal-name")
|
||||
.scope("thing.write") // must have at least one scope
|
||||
.build();
|
||||
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService(authorizationConsent, authorizationConsent))
|
||||
.withMessage("The authorizationConsent must be unique. Found duplicate, with registered client id: [client-id] and principal name: [principal-name]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenConsentNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.consentService.save(null))
|
||||
.withMessage("authorizationConsent cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenConsentNewThenSaved() {
|
||||
OAuth2AuthorizationConsent expectedConsent = OAuth2AuthorizationConsent
|
||||
.withId("new-client", "new-principal")
|
||||
.authority(new SimpleGrantedAuthority("new.authority"))
|
||||
.build();
|
||||
|
||||
this.consentService.save(expectedConsent);
|
||||
|
||||
OAuth2AuthorizationConsent consent =
|
||||
this.consentService.findById("new-client", "new-principal");
|
||||
assertThat(consent).isEqualTo(expectedConsent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenConsentExistsThenUpdated() {
|
||||
OAuth2AuthorizationConsent expectedConsent = OAuth2AuthorizationConsent
|
||||
.from(CONSENT)
|
||||
.authority(new SimpleGrantedAuthority("new.authority"))
|
||||
.build();
|
||||
|
||||
this.consentService.save(expectedConsent);
|
||||
|
||||
OAuth2AuthorizationConsent consent =
|
||||
this.consentService.findById(CLIENT_ID, PRINCIPAL_NAME);
|
||||
assertThat(consent).isEqualTo(expectedConsent);
|
||||
assertThat(consent).isNotEqualTo(CONSENT);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.consentService.remove(null))
|
||||
.withMessage("authorizationConsent cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeWhenConsentProvidedThenRemoved() {
|
||||
this.consentService.remove(CONSENT);
|
||||
|
||||
assertThat(this.consentService.findById(CLIENT_ID, PRINCIPAL_NAME))
|
||||
.isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findWhenRegisteredClientIdNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.consentService.findById(null, "some-user"))
|
||||
.withMessage("registeredClientId cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findWhenPrincipalNameNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.consentService.findById("some-client", null))
|
||||
.withMessage("principalName cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findWhenConsentExistsThenFound() {
|
||||
assertThat(this.consentService.findById(CLIENT_ID, PRINCIPAL_NAME))
|
||||
.isEqualTo(CONSENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findWhenConsentDoesNotExistThenNull() {
|
||||
this.consentService.save(CONSENT);
|
||||
|
||||
assertThat(this.consentService.findById("unknown-client", PRINCIPAL_NAME)).isNull();
|
||||
assertThat(this.consentService.findById(CLIENT_ID, "unkown-user")).isNull();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2020-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.server.authorization;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link InMemoryOAuth2AuthorizationConsentService}.
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class InMemoryOAuth2AuthorizationConsentServiceTests {
|
||||
private static final String REGISTERED_CLIENT_ID = "registered-client-id";
|
||||
private static final String PRINCIPAL_NAME = "principal-name";
|
||||
private static final OAuth2AuthorizationConsent AUTHORIZATION_CONSENT =
|
||||
OAuth2AuthorizationConsent.withId(REGISTERED_CLIENT_ID, PRINCIPAL_NAME)
|
||||
.authority(new SimpleGrantedAuthority("some.authority"))
|
||||
.build();
|
||||
|
||||
private InMemoryOAuth2AuthorizationConsentService authorizationConsentService;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.authorizationConsentService = new InMemoryOAuth2AuthorizationConsentService();
|
||||
this.authorizationConsentService.save(AUTHORIZATION_CONSENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorVarargsWhenAuthorizationConsentNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService((OAuth2AuthorizationConsent) null))
|
||||
.withMessage("authorizationConsent cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorListWhenAuthorizationConsentsNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService((List<OAuth2AuthorizationConsent>) null))
|
||||
.withMessage("authorizationConsents cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenDuplicateAuthorizationConsentsThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new InMemoryOAuth2AuthorizationConsentService(AUTHORIZATION_CONSENT, AUTHORIZATION_CONSENT))
|
||||
.withMessage("The authorizationConsent must be unique. Found duplicate, with registered client id: [registered-client-id] and principal name: [principal-name]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenAuthorizationConsentNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizationConsentService.save(null))
|
||||
.withMessage("authorizationConsent cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenAuthorizationConsentNewThenSaved() {
|
||||
OAuth2AuthorizationConsent expectedAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId("new-client", "new-principal")
|
||||
.authority(new SimpleGrantedAuthority("new.authority"))
|
||||
.build();
|
||||
|
||||
this.authorizationConsentService.save(expectedAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationConsent authorizationConsent =
|
||||
this.authorizationConsentService.findById("new-client", "new-principal");
|
||||
assertThat(authorizationConsent).isEqualTo(expectedAuthorizationConsent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWhenAuthorizationConsentExistsThenUpdated() {
|
||||
OAuth2AuthorizationConsent expectedAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.from(AUTHORIZATION_CONSENT)
|
||||
.authority(new SimpleGrantedAuthority("new.authority"))
|
||||
.build();
|
||||
|
||||
this.authorizationConsentService.save(expectedAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationConsent authorizationConsent =
|
||||
this.authorizationConsentService.findById(
|
||||
AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName());
|
||||
assertThat(authorizationConsent).isEqualTo(expectedAuthorizationConsent);
|
||||
assertThat(authorizationConsent).isNotEqualTo(AUTHORIZATION_CONSENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeWhenAuthorizationConsentNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizationConsentService.remove(null))
|
||||
.withMessage("authorizationConsent cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeWhenAuthorizationConsentProvidedThenRemoved() {
|
||||
this.authorizationConsentService.remove(AUTHORIZATION_CONSENT);
|
||||
assertThat(this.authorizationConsentService.findById(
|
||||
AUTHORIZATION_CONSENT.getRegisteredClientId(), AUTHORIZATION_CONSENT.getPrincipalName()))
|
||||
.isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdWhenRegisteredClientIdNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizationConsentService.findById(null, "some-user"))
|
||||
.withMessage("registeredClientId cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdWhenPrincipalNameNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.authorizationConsentService.findById("some-client", null))
|
||||
.withMessage("principalName cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdWhenAuthorizationConsentExistsThenFound() {
|
||||
assertThat(this.authorizationConsentService.findById(REGISTERED_CLIENT_ID, PRINCIPAL_NAME))
|
||||
.isEqualTo(AUTHORIZATION_CONSENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findByIdWhenAuthorizationConsentDoesNotExistThenNull() {
|
||||
this.authorizationConsentService.save(AUTHORIZATION_CONSENT);
|
||||
assertThat(this.authorizationConsentService.findById("unknown-client", PRINCIPAL_NAME)).isNull();
|
||||
assertThat(this.authorizationConsentService.findById(REGISTERED_CLIENT_ID, "unknown-user")).isNull();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.security.oauth2.server.authorization;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -26,7 +27,8 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||
*
|
||||
* @author Daniel Garnier-Moiroux
|
||||
*/
|
||||
public class OAuth2AuthorizationConsentTest {
|
||||
public class OAuth2AuthorizationConsentTests {
|
||||
|
||||
@Test
|
||||
public void fromWhenAuthorizationConsentNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
@@ -35,14 +37,14 @@ public class OAuth2AuthorizationConsentTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClientIdAndPrincipalWhenClientIdNullThenThrowIllegalArgumentException() {
|
||||
public void withIdWhenRegisteredClientIdNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationConsent.withId(null, "some-user"))
|
||||
.withMessage("registeredClientId cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withClientIdAndPrincipalWhenPrincipalNullThenThrowIllegalArgumentException() {
|
||||
public void withIdWhenPrincipalNameNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> OAuth2AuthorizationConsent.withId("some-client", null))
|
||||
.withMessage("principalName cannot be empty");
|
||||
@@ -58,21 +60,21 @@ public class OAuth2AuthorizationConsentTest {
|
||||
|
||||
@Test
|
||||
public void buildWhenAllAttributesAreProvidedThenAllAttributesAreSet() {
|
||||
OAuth2AuthorizationConsent consent = OAuth2AuthorizationConsent
|
||||
.withId("some-client", "some-user")
|
||||
.scope("resource.read")
|
||||
.scope("resource.write")
|
||||
.authority(new SimpleGrantedAuthority("CLAIM_email"))
|
||||
.build();
|
||||
OAuth2AuthorizationConsent authorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId("some-client", "some-user")
|
||||
.scope("resource.read")
|
||||
.scope("resource.write")
|
||||
.authority(new SimpleGrantedAuthority("CLAIM_email"))
|
||||
.build();
|
||||
|
||||
assertThat(consent.getPrincipalName()).isEqualTo("some-user");
|
||||
assertThat(consent.getRegisteredClientId()).isEqualTo("some-client");
|
||||
assertThat(consent.getScopes())
|
||||
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo("some-client");
|
||||
assertThat(authorizationConsent.getPrincipalName()).isEqualTo("some-user");
|
||||
assertThat(authorizationConsent.getScopes())
|
||||
.containsExactlyInAnyOrder(
|
||||
"resource.read",
|
||||
"resource.write"
|
||||
);
|
||||
assertThat(consent.getAuthorities())
|
||||
assertThat(authorizationConsent.getAuthorities())
|
||||
.containsExactlyInAnyOrder(
|
||||
new SimpleGrantedAuthority("SCOPE_resource.read"),
|
||||
new SimpleGrantedAuthority("SCOPE_resource.write"),
|
||||
@@ -82,18 +84,20 @@ public class OAuth2AuthorizationConsentTest {
|
||||
|
||||
@Test
|
||||
public void fromWhenAuthorizationConsentProvidedThenCopied() {
|
||||
OAuth2AuthorizationConsent previousConsent = OAuth2AuthorizationConsent
|
||||
.withId("some-client", "some-principal")
|
||||
.scope("first.scope")
|
||||
.scope("second.scope")
|
||||
.authority(new SimpleGrantedAuthority("CLAIM_email"))
|
||||
.build();
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId("some-client", "some-principal")
|
||||
.scope("first.scope")
|
||||
.scope("second.scope")
|
||||
.authority(new SimpleGrantedAuthority("CLAIM_email"))
|
||||
.build();
|
||||
|
||||
OAuth2AuthorizationConsent consent = OAuth2AuthorizationConsent.from(previousConsent).build();
|
||||
OAuth2AuthorizationConsent authorizationConsent =
|
||||
OAuth2AuthorizationConsent.from(previousAuthorizationConsent)
|
||||
.build();
|
||||
|
||||
assertThat(consent.getPrincipalName()).isEqualTo("some-principal");
|
||||
assertThat(consent.getRegisteredClientId()).isEqualTo("some-client");
|
||||
assertThat(consent.getAuthorities())
|
||||
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo("some-client");
|
||||
assertThat(authorizationConsent.getPrincipalName()).isEqualTo("some-principal");
|
||||
assertThat(authorizationConsent.getAuthorities())
|
||||
.containsExactlyInAnyOrder(
|
||||
new SimpleGrantedAuthority("SCOPE_first.scope"),
|
||||
new SimpleGrantedAuthority("SCOPE_second.scope"),
|
||||
@@ -103,15 +107,16 @@ public class OAuth2AuthorizationConsentTest {
|
||||
|
||||
@Test
|
||||
public void authoritiesThenCustomizesAuthorities() {
|
||||
OAuth2AuthorizationConsent consent = OAuth2AuthorizationConsent
|
||||
.withId("some-client", "some-user")
|
||||
.authority(new SimpleGrantedAuthority("some.authority"))
|
||||
.authorities(authorities -> {
|
||||
authorities.clear();
|
||||
authorities.add(new SimpleGrantedAuthority("other.authority"));
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizationConsent authorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId("some-client", "some-user")
|
||||
.authority(new SimpleGrantedAuthority("some.authority"))
|
||||
.authorities(authorities -> {
|
||||
authorities.clear();
|
||||
authorities.add(new SimpleGrantedAuthority("other.authority"));
|
||||
})
|
||||
.build();
|
||||
|
||||
assertThat(consent.getAuthorities()).containsExactly(new SimpleGrantedAuthority("other.authority"));
|
||||
assertThat(authorizationConsent.getAuthorities()).containsExactly(new SimpleGrantedAuthority("other.authority"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -88,16 +89,17 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
private static final String PKCE_ERROR_URI = "https://tools.ietf.org/html/rfc7636%23section-4.4.1";
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
private OAuth2AuthorizationEndpointFilter filter;
|
||||
private OAuth2AuthorizationConsentService consentService;
|
||||
private TestingAuthenticationToken authentication;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
this.consentService = mock(OAuth2AuthorizationConsentService.class);
|
||||
this.filter = new OAuth2AuthorizationEndpointFilter(this.registeredClientRepository, this.authorizationService, this.consentService);
|
||||
this.authorizationConsentService = mock(OAuth2AuthorizationConsentService.class);
|
||||
this.filter = new OAuth2AuthorizationEndpointFilter(
|
||||
this.registeredClientRepository, this.authorizationService, this.authorizationConsentService);
|
||||
this.authentication = new TestingAuthenticationToken("principalName", "password");
|
||||
this.authentication.setAuthenticated(true);
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
@@ -112,28 +114,28 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(null, this.authorizationService, this.consentService))
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(null, this.authorizationService, this.authorizationConsentService))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("registeredClientRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(this.registeredClientRepository, null, this.consentService))
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(this.registeredClientRepository, null, this.authorizationConsentService))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenConsentServiceNullThenThrowIllegalArgumentException() {
|
||||
public void constructorWhenAuthorizationConsentServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(this.registeredClientRepository, this.authorizationService, (OAuth2AuthorizationConsentService) null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("consentService cannot be null");
|
||||
.hasMessage("authorizationConsentService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationEndpointUriNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(this.registeredClientRepository, this.authorizationService, this.consentService, null))
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationEndpointFilter(this.registeredClientRepository, this.authorizationService, this.authorizationConsentService, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationEndpointUri cannot be empty");
|
||||
}
|
||||
@@ -592,7 +594,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
@@ -601,6 +603,8 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
@@ -640,7 +644,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
})
|
||||
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
@@ -656,8 +660,6 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
assertThat(response.getContentAsString()).contains(scopeCheckbox("message.read"));
|
||||
assertThat(response.getContentAsString()).contains(scopeCheckbox("message.write"));
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -673,19 +675,17 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
})
|
||||
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationConsent previousConsent = createConsent(
|
||||
OAuth2AuthorizationConsent previousConsent = createAuthorizationConsent(
|
||||
registeredClient.getClientId(),
|
||||
this.authentication.getName(),
|
||||
Arrays.asList(previouslyApprovedScope, unrelatedPreviouslyApprovedScope)
|
||||
);
|
||||
when(this.consentService.findById(
|
||||
eq(registeredClient.getClientId()),
|
||||
eq(this.authentication.getName())))
|
||||
when(this.authorizationConsentService.findById(
|
||||
eq(registeredClient.getId()), eq(this.authentication.getName())))
|
||||
.thenReturn(previousConsent);
|
||||
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
@@ -704,7 +704,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenUserConsentRequiredAndCustomConsentUriAndAuthorizationRequestThenRedirects() throws Exception {
|
||||
this.filter.setUserConsentUri("/consent");
|
||||
this.filter.setUserConsentUri("/oauth2/consent");
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
@@ -713,7 +713,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
})
|
||||
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
@@ -736,7 +736,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
String[] redirectScopes = consentRedirectUri.getQueryParams().getFirst(OAuth2ParameterNames.SCOPE).split(" ");
|
||||
String redirectState = consentRedirectUri.getQueryParams().getFirst(OAuth2ParameterNames.STATE);
|
||||
|
||||
assertThat(consentRedirectUri.getPath()).isEqualTo("/consent");
|
||||
assertThat(consentRedirectUri.getPath()).isEqualTo("/oauth2/consent");
|
||||
assertThat(consentRedirectUri.getQueryParams().getFirst(OAuth2ParameterNames.CLIENT_ID)).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(redirectScopes).containsExactlyInAnyOrder("message.read", "message.write");
|
||||
assertThat(redirectState).isEqualTo(authorization.getAttribute(OAuth2ParameterNames.STATE));
|
||||
@@ -752,15 +752,14 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
})
|
||||
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationConsent previousConsent = createConsent(
|
||||
OAuth2AuthorizationConsent authorizationConsent = createAuthorizationConsent(
|
||||
registeredClient.getClientId(), this.authentication.getName(), Arrays.asList("message.read", "message.write")
|
||||
);
|
||||
when(this.consentService.findById(
|
||||
eq(registeredClient.getClientId()),
|
||||
eq(this.authentication.getName())))
|
||||
.thenReturn(previousConsent);
|
||||
when(this.authorizationConsentService.findById(
|
||||
eq(registeredClient.getId()), eq(this.authentication.getName())))
|
||||
.thenReturn(authorizationConsent);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -1011,7 +1010,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
MockHttpServletRequest request = createUserConsentRequest(registeredClient);
|
||||
@@ -1020,13 +1019,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> consentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
|
||||
verify(this.consentService).save(consentCaptor.capture());
|
||||
OAuth2AuthorizationConsent consent = consentCaptor.getValue();
|
||||
assertThat(consent.getPrincipalName()).isEqualTo(this.authentication.getName());
|
||||
assertThat(consent.getRegisteredClientId()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(consent.getScopes()).containsExactlyInAnyOrder("message.read", "message.write");
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue();
|
||||
assertThat(authorizationConsent.getPrincipalName()).isEqualTo(this.authentication.getName());
|
||||
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo(registeredClient.getId());
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder("message.read", "message.write");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1041,7 +1040,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
MockHttpServletRequest request = createUserConsentRequest(registeredClient);
|
||||
@@ -1050,7 +1049,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verify(this.consentService, never()).save(any());
|
||||
verify(this.authorizationConsentService, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1069,18 +1068,17 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationConsent previousConsent =
|
||||
createConsent(
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
createAuthorizationConsent(
|
||||
registeredClient.getClientId(),
|
||||
this.authentication.getName(),
|
||||
Collections.singleton("message.read")
|
||||
);
|
||||
when(this.consentService.findById(
|
||||
eq(registeredClient.getClientId()),
|
||||
eq(this.authentication.getName())))
|
||||
.thenReturn(previousConsent);
|
||||
when(this.authorizationConsentService.findById(
|
||||
eq(registeredClient.getId()), eq(this.authentication.getName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
MockHttpServletRequest request = createUserConsentRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -1088,13 +1086,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> consentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
|
||||
verify(this.consentService).save(consentCaptor.capture());
|
||||
OAuth2AuthorizationConsent consent = consentCaptor.getValue();
|
||||
assertThat(consent.getPrincipalName()).isEqualTo(this.authentication.getName());
|
||||
assertThat(consent.getRegisteredClientId()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(consent.getScopes()).containsExactlyInAnyOrder("message.read", "message.write");
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue();
|
||||
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authorizationConsent.getPrincipalName()).isEqualTo(this.authentication.getName());
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrder("message.read", "message.write");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1116,18 +1114,17 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationConsent previousConsent =
|
||||
createConsent(
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
createAuthorizationConsent(
|
||||
registeredClient.getClientId(),
|
||||
this.authentication.getName(),
|
||||
Arrays.asList(previouslyApprovedScope, unrelatedPreviouslyApprovedScope)
|
||||
);
|
||||
when(this.consentService.findById(
|
||||
eq(registeredClient.getClientId()),
|
||||
eq(this.authentication.getName())))
|
||||
.thenReturn(previousConsent);
|
||||
when(this.authorizationConsentService.findById(
|
||||
eq(registeredClient.getId()), eq(this.authentication.getName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
MockHttpServletRequest request = createUserConsentRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -1313,18 +1310,14 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
request.addParameter(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
||||
}
|
||||
|
||||
private static OAuth2AuthorizationConsent createConsent(
|
||||
String registeredClientId,
|
||||
String prinicpalName,
|
||||
Collection<String> scopes
|
||||
) {
|
||||
OAuth2AuthorizationConsent.Builder consentBuilder = OAuth2AuthorizationConsent
|
||||
.withId(registeredClientId, prinicpalName);
|
||||
private static OAuth2AuthorizationConsent createAuthorizationConsent(String registeredClientId,
|
||||
String principalName, Collection<String> scopes) {
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder =
|
||||
OAuth2AuthorizationConsent.withId(registeredClientId, principalName);
|
||||
for (String scope : scopes) {
|
||||
consentBuilder.scope(scope);
|
||||
authorizationConsentBuilder.scope(scope);
|
||||
}
|
||||
return consentBuilder.build();
|
||||
|
||||
return authorizationConsentBuilder.build();
|
||||
}
|
||||
|
||||
private static MockHttpServletRequest createUserConsentRequest(RegisteredClient registeredClient) {
|
||||
@@ -1357,4 +1350,5 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
scope
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-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,7 +20,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.2
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class OAuth2AuthorizationServerCustomConsentPageApplication {
|
||||
|
||||
@@ -15,10 +15,14 @@
|
||||
*/
|
||||
package sample.config;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import sample.jose.Jwks;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
@@ -37,14 +41,10 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import sample.jose.Jwks;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.0.1
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class AuthorizationServerConfig {
|
||||
@@ -54,7 +54,7 @@ public class AuthorizationServerConfig {
|
||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
|
||||
new OAuth2AuthorizationServerConfigurer<>();
|
||||
authorizationServerConfigurer.consentPage("/consent");
|
||||
authorizationServerConfigurer.consentPage("/oauth2/consent");
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer
|
||||
.getEndpointsMatcher();
|
||||
|
||||
@@ -102,8 +102,9 @@ public class AuthorizationServerConfig {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public OAuth2AuthorizationConsentService authorizationService() {
|
||||
public OAuth2AuthorizationConsentService authorizationConsentService() {
|
||||
// Will be used by the ConsentController
|
||||
return new InMemoryOAuth2AuthorizationConsentService();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-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.
|
||||
@@ -28,12 +28,11 @@ import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
*/
|
||||
@EnableWebSecurity
|
||||
public class DefaultSecurityConfig {
|
||||
|
||||
// formatter:off
|
||||
// @formatter:off
|
||||
@Bean
|
||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
@@ -43,7 +42,7 @@ public class DefaultSecurityConfig {
|
||||
.formLogin(withDefaults());
|
||||
return http.build();
|
||||
}
|
||||
// formatter:on
|
||||
// @formatter:on
|
||||
|
||||
// @formatter:off
|
||||
@Bean
|
||||
|
||||
@@ -15,12 +15,6 @@
|
||||
*/
|
||||
package sample.jose;
|
||||
|
||||
import com.nimbusds.jose.jwk.Curve;
|
||||
import com.nimbusds.jose.jwk.ECKey;
|
||||
import com.nimbusds.jose.jwk.OctetSequenceKey;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.security.KeyPair;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
@@ -28,9 +22,15 @@ import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.nimbusds.jose.jwk.Curve;
|
||||
import com.nimbusds.jose.jwk.ECKey;
|
||||
import com.nimbusds.jose.jwk.OctetSequenceKey;
|
||||
import com.nimbusds.jose.jwk.RSAKey;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public final class Jwks {
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package sample.jose;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
@@ -25,9 +23,11 @@ import java.security.spec.ECParameterSpec;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.security.spec.EllipticCurve;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.0
|
||||
*/
|
||||
final class KeyGeneratorUtils {
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package sample.web;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
@@ -25,6 +26,8 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -33,39 +36,46 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
/**
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @since 0.1.2
|
||||
*/
|
||||
@Controller
|
||||
public class ConsentController {
|
||||
|
||||
public class AuthorizationConsentController {
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
private final OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
|
||||
public ConsentController(OAuth2AuthorizationConsentService authorizationConsentService) {
|
||||
public AuthorizationConsentController(RegisteredClientRepository registeredClientRepository,
|
||||
OAuth2AuthorizationConsentService authorizationConsentService) {
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
this.authorizationConsentService = authorizationConsentService;
|
||||
}
|
||||
|
||||
@GetMapping(value = "/consent")
|
||||
public String consent(
|
||||
Principal principal,
|
||||
Model model,
|
||||
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
|
||||
@GetMapping(value = "/oauth2/consent")
|
||||
public String consent(Principal principal, Model model,
|
||||
@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
|
||||
@RequestParam(OAuth2ParameterNames.STATE) String state
|
||||
) {
|
||||
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
|
||||
@RequestParam(OAuth2ParameterNames.STATE) String state) {
|
||||
|
||||
// Remove scopes that were already approved
|
||||
Set<String> scopesToApprove = new HashSet<>();
|
||||
Set<String> previouslyApprovedScopes = new HashSet<>();
|
||||
OAuth2AuthorizationConsent previousConsent = this.authorizationConsentService.findById(clientId, principal.getName());
|
||||
for (String scopeFromRequest : StringUtils.delimitedListToStringArray(scope, " ")) {
|
||||
if (previousConsent != null && previousConsent.getScopes().contains(scopeFromRequest)) {
|
||||
previouslyApprovedScopes.add(scopeFromRequest);
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent =
|
||||
this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
|
||||
Set<String> authorizedScopes;
|
||||
if (currentAuthorizationConsent != null) {
|
||||
authorizedScopes = currentAuthorizationConsent.getScopes();
|
||||
} else {
|
||||
authorizedScopes = Collections.emptySet();
|
||||
}
|
||||
for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
|
||||
if (authorizedScopes.contains(requestedScope)) {
|
||||
previouslyApprovedScopes.add(requestedScope);
|
||||
} else {
|
||||
scopesToApprove.add(scopeFromRequest);
|
||||
scopesToApprove.add(requestedScope);
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute("state", state);
|
||||
model.addAttribute("clientId", clientId);
|
||||
model.addAttribute("state", state);
|
||||
model.addAttribute("scopes", withDescription(scopesToApprove));
|
||||
model.addAttribute("previouslyApprovedScopes", withDescription(previouslyApprovedScopes));
|
||||
model.addAttribute("principalName", principal.getName());
|
||||
@@ -73,18 +83,15 @@ public class ConsentController {
|
||||
return "consent";
|
||||
}
|
||||
|
||||
private Set<ScopeWithDescription> withDescription(Set<String> scopes) {
|
||||
private static Set<ScopeWithDescription> withDescription(Set<String> scopes) {
|
||||
return scopes
|
||||
.stream()
|
||||
.map(ScopeWithDescription::new)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
private static class ScopeWithDescription {
|
||||
public final String scope;
|
||||
public final String description;
|
||||
|
||||
private final static String DEFAULT_DESCRIPTION = "UNKNOWN SCOPE - We cannot provide information about this permission, use caution when granting this.";
|
||||
public static class ScopeWithDescription {
|
||||
private static final String DEFAULT_DESCRIPTION = "UNKNOWN SCOPE - We cannot provide information about this permission, use caution when granting this.";
|
||||
private static final Map<String, String> scopeDescriptions = new HashMap<>();
|
||||
static {
|
||||
scopeDescriptions.put(
|
||||
@@ -101,6 +108,9 @@ public class ConsentController {
|
||||
);
|
||||
}
|
||||
|
||||
public final String scope;
|
||||
public final String description;
|
||||
|
||||
ScopeWithDescription(String scope) {
|
||||
this.scope = scope;
|
||||
this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
|
||||
Reference in New Issue
Block a user