All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter Maven / Gradle / Ivy

There is a newer version: 0.1.2
Show newest version
/*
 * Copyright 2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.security.oauth2.server.authorization.web;

import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TokenType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
 * which handles the processing of the OAuth 2.0 Authorization Request.
 *
 * @author Joe Grandja
 * @author Paurav Munshi
 * @author Daniel Garnier-Moiroux
 * @since 0.0.1
 * @see RegisteredClientRepository
 * @see OAuth2AuthorizationService
 * @see OAuth2Authorization
 * @see Section 4.1 Authorization Code Grant
 * @see Section 4.1.1 Authorization Request
 * @see Section 4.1.2 Authorization Response
 */
public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
	/**
	 * The default endpoint {@code URI} for authorization requests.
	 */
	public static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";

	private static final String PKCE_ERROR_URI = "https://tools.ietf.org/html/rfc7636#section-4.4.1";

	private final RegisteredClientRepository registeredClientRepository;
	private final OAuth2AuthorizationService authorizationService;
	private final RequestMatcher authorizationRequestMatcher;
	private final RequestMatcher userConsentMatcher;
	private final StringKeyGenerator codeGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
	private final StringKeyGenerator stateGenerator = new Base64StringKeyGenerator(Base64.getUrlEncoder());
	private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

	/**
	 * Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided parameters.
	 *
	 * @param registeredClientRepository the repository of registered clients
	 * @param authorizationService the authorization service
	 */
	public OAuth2AuthorizationEndpointFilter(RegisteredClientRepository registeredClientRepository,
			OAuth2AuthorizationService authorizationService) {
		this(registeredClientRepository, authorizationService, DEFAULT_AUTHORIZATION_ENDPOINT_URI);
	}

	/**
	 * Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided parameters.
	 *
	 * @param registeredClientRepository the repository of registered clients
	 * @param authorizationService the authorization service
	 * @param authorizationEndpointUri the endpoint {@code URI} for authorization requests
	 */
	public OAuth2AuthorizationEndpointFilter(RegisteredClientRepository registeredClientRepository,
			OAuth2AuthorizationService authorizationService, String authorizationEndpointUri) {
		Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
		Assert.notNull(authorizationService, "authorizationService cannot be null");
		Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
		this.registeredClientRepository = registeredClientRepository;
		this.authorizationService = authorizationService;
		this.authorizationRequestMatcher = new AntPathRequestMatcher(
				authorizationEndpointUri, HttpMethod.GET.name());
		this.userConsentMatcher = new AntPathRequestMatcher(
				authorizationEndpointUri, HttpMethod.POST.name());
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (this.authorizationRequestMatcher.matches(request)) {
			processAuthorizationRequest(request, response, filterChain);
		} else if (this.userConsentMatcher.matches(request)) {
			processUserConsent(request, response);
		} else {
			filterChain.doFilter(request, response);
		}
	}

	private void processAuthorizationRequest(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		OAuth2AuthorizationRequestContext authorizationRequestContext =
				new OAuth2AuthorizationRequestContext(
						request.getRequestURL().toString(),
						OAuth2EndpointUtils.getParameters(request));

		validateAuthorizationRequest(authorizationRequestContext);

		if (authorizationRequestContext.hasError()) {
			if (authorizationRequestContext.isRedirectOnError()) {
				sendErrorResponse(request, response, authorizationRequestContext.resolveRedirectUri(),
						authorizationRequestContext.getError(), authorizationRequestContext.getState());
			} else {
				sendErrorResponse(response, authorizationRequestContext.getError());
			}
			return;
		}

		// ---------------
		// The request is valid - ensure the resource owner is authenticated
		// ---------------

		Authentication principal = SecurityContextHolder.getContext().getAuthentication();
		if (!isPrincipalAuthenticated(principal)) {
			// Pass through the chain with the expectation that the authentication process
			// will commence via AuthenticationEntryPoint
			filterChain.doFilter(request, response);
			return;
		}

		RegisteredClient registeredClient = authorizationRequestContext.getRegisteredClient();
		OAuth2AuthorizationRequest authorizationRequest = authorizationRequestContext.buildAuthorizationRequest();
		OAuth2Authorization.Builder builder = OAuth2Authorization.withRegisteredClient(registeredClient)
				.principalName(principal.getName())
				.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST, authorizationRequest);

		if (registeredClient.getClientSettings().requireUserConsent()) {
			String state = this.stateGenerator.generateKey();
			OAuth2Authorization authorization = builder
					.attribute(OAuth2AuthorizationAttributeNames.STATE, state)
					.build();
			this.authorizationService.save(authorization);

			// TODO Need to remove 'in-flight' authorization if consent step is not completed (e.g. approved or cancelled)

			UserConsentPage.displayConsent(request, response, registeredClient, authorization);
		} else {
			Instant issuedAt = Instant.now();
			Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES);		// TODO Allow configuration for authorization code time-to-live
			OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
					this.codeGenerator.generateKey(), issuedAt, expiresAt);
			OAuth2Authorization authorization = builder
					.tokens(OAuth2Tokens.builder().token(authorizationCode).build())
					.attribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, authorizationRequest.getScopes())
					.build();
			this.authorizationService.save(authorization);

//			TODO security checks for code parameter
//			The authorization code MUST expire shortly after it is issued to mitigate the risk of leaks.
//			A maximum authorization code lifetime of 10 minutes is RECOMMENDED.
//			The client MUST NOT use the authorization code more than once.
//			If an authorization code is used more than once, the authorization server MUST deny the request
//			and SHOULD revoke (when possible) all tokens previously issued based on that authorization code.
//			The authorization code is bound to the client identifier and redirection URI.

			sendAuthorizationResponse(request, response,
					authorizationRequestContext.resolveRedirectUri(), authorizationCode, authorizationRequest.getState());
		}
	}

	private void processUserConsent(HttpServletRequest request, HttpServletResponse response)
			throws IOException {

		UserConsentRequestContext userConsentRequestContext =
				new UserConsentRequestContext(
						request.getRequestURL().toString(),
						OAuth2EndpointUtils.getParameters(request));

		validateUserConsentRequest(userConsentRequestContext);

		if (userConsentRequestContext.hasError()) {
			if (userConsentRequestContext.isRedirectOnError()) {
				sendErrorResponse(request, response, userConsentRequestContext.resolveRedirectUri(),
						userConsentRequestContext.getError(), userConsentRequestContext.getState());
			} else {
				sendErrorResponse(response, userConsentRequestContext.getError());
			}
			return;
		}

		if (!UserConsentPage.isConsentApproved(request)) {
			this.authorizationService.remove(userConsentRequestContext.getAuthorization());
			OAuth2Error error = createError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID);
			sendErrorResponse(request, response, userConsentRequestContext.resolveRedirectUri(),
					error, userConsentRequestContext.getAuthorizationRequest().getState());
			return;
		}

		Instant issuedAt = Instant.now();
		Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES);		// TODO Allow configuration for authorization code time-to-live
		OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
				this.codeGenerator.generateKey(), issuedAt, expiresAt);
		OAuth2Authorization authorization = OAuth2Authorization.from(userConsentRequestContext.getAuthorization())
				.tokens(OAuth2Tokens.builder().token(authorizationCode).build())
				.attributes(attrs -> {
					attrs.remove(OAuth2AuthorizationAttributeNames.STATE);
					attrs.put(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES, userConsentRequestContext.getScopes());
				})
				.build();
		this.authorizationService.save(authorization);

		sendAuthorizationResponse(request, response, userConsentRequestContext.resolveRedirectUri(),
				authorizationCode, userConsentRequestContext.getAuthorizationRequest().getState());
	}

	private void validateAuthorizationRequest(OAuth2AuthorizationRequestContext authorizationRequestContext) {
		// ---------------
		// Validate the request to ensure all required parameters are present and valid
		// ---------------

		// client_id (REQUIRED)
		if (!StringUtils.hasText(authorizationRequestContext.getClientId()) ||
				authorizationRequestContext.getParameters().get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
			return;
		}
		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
				authorizationRequestContext.getClientId());
		if (registeredClient == null) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
			return;
		} else if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID));
			return;
		}
		authorizationRequestContext.setRegisteredClient(registeredClient);

		// redirect_uri (OPTIONAL)
		if (StringUtils.hasText(authorizationRequestContext.getRedirectUri())) {
			if (!registeredClient.getRedirectUris().contains(authorizationRequestContext.getRedirectUri()) ||
					authorizationRequestContext.getParameters().get(OAuth2ParameterNames.REDIRECT_URI).size() != 1) {
				authorizationRequestContext.setError(
						createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
				return;
			}
		} else if (registeredClient.getRedirectUris().size() != 1) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
			return;
		}
		authorizationRequestContext.setRedirectOnError(true);

		// response_type (REQUIRED)
		if (!StringUtils.hasText(authorizationRequestContext.getResponseType()) ||
				authorizationRequestContext.getParameters().get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE));
			return;
		} else if (!authorizationRequestContext.getResponseType().equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE));
			return;
		}

		// scope (OPTIONAL)
		Set requestedScopes = authorizationRequestContext.getScopes();
		Set allowedScopes = registeredClient.getScopes();
		if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE));
			return;
		}

		// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
		String codeChallenge = authorizationRequestContext.getParameters().getFirst(PkceParameterNames.CODE_CHALLENGE);
		if (StringUtils.hasText(codeChallenge)) {
			if (authorizationRequestContext.getParameters().get(PkceParameterNames.CODE_CHALLENGE).size() != 1) {
				authorizationRequestContext.setError(
						createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI));
				return;
			}

			String codeChallengeMethod = authorizationRequestContext.getParameters().getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
			if (StringUtils.hasText(codeChallengeMethod)) {
				if (authorizationRequestContext.getParameters().get(PkceParameterNames.CODE_CHALLENGE_METHOD).size() != 1 ||
						(!"S256".equals(codeChallengeMethod) && !"plain".equals(codeChallengeMethod))) {
					authorizationRequestContext.setError(
							createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI));
					return;
				}
			}
		} else if (registeredClient.getClientSettings().requireProofKey()) {
			authorizationRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI));
			return;
		}
	}

	private void validateUserConsentRequest(UserConsentRequestContext userConsentRequestContext) {
		// ---------------
		// Validate the request to ensure all required parameters are present and valid
		// ---------------

		// state (REQUIRED)
		if (!StringUtils.hasText(userConsentRequestContext.getState()) ||
				userConsentRequestContext.getParameters().get(OAuth2ParameterNames.STATE).size() != 1) {
			userConsentRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE));
			return;
		}
		OAuth2Authorization authorization = this.authorizationService.findByToken(
				userConsentRequestContext.getState(), new TokenType(OAuth2AuthorizationAttributeNames.STATE));
		if (authorization == null) {
			userConsentRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE));
			return;
		}
		userConsentRequestContext.setAuthorization(authorization);

		// The 'in-flight' authorization must be associated to the current principal
		Authentication principal = SecurityContextHolder.getContext().getAuthentication();
		if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
			userConsentRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE));
			return;
		}

		// client_id (REQUIRED)
		if (!StringUtils.hasText(userConsentRequestContext.getClientId()) ||
				userConsentRequestContext.getParameters().get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
			userConsentRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
			return;
		}
		RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
				userConsentRequestContext.getClientId());
		if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
			userConsentRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID));
			return;
		}
		userConsentRequestContext.setRegisteredClient(registeredClient);
		userConsentRequestContext.setRedirectOnError(true);

		// scope (OPTIONAL)
		Set requestedScopes = userConsentRequestContext.getAuthorizationRequest().getScopes();
		Set authorizedScopes = userConsentRequestContext.getScopes();
		if (!authorizedScopes.isEmpty() && !requestedScopes.containsAll(authorizedScopes)) {
			userConsentRequestContext.setError(
					createError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE));
			return;
		}
	}

	private void sendAuthorizationResponse(HttpServletRequest request, HttpServletResponse response,
			String redirectUri, OAuth2AuthorizationCode authorizationCode, String state) throws IOException {

		UriComponentsBuilder uriBuilder = UriComponentsBuilder
				.fromUriString(redirectUri)
				.queryParam(OAuth2ParameterNames.CODE, authorizationCode.getTokenValue());
		if (StringUtils.hasText(state)) {
			uriBuilder.queryParam(OAuth2ParameterNames.STATE, state);
		}
		this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
	}

	private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
			String redirectUri, OAuth2Error error, String state) throws IOException {

		UriComponentsBuilder uriBuilder = UriComponentsBuilder
				.fromUriString(redirectUri)
				.queryParam(OAuth2ParameterNames.ERROR, error.getErrorCode());
		if (StringUtils.hasText(error.getDescription())) {
			uriBuilder.queryParam(OAuth2ParameterNames.ERROR_DESCRIPTION, error.getDescription());
		}
		if (StringUtils.hasText(error.getUri())) {
			uriBuilder.queryParam(OAuth2ParameterNames.ERROR_URI, error.getUri());
		}
		if (StringUtils.hasText(state)) {
			uriBuilder.queryParam(OAuth2ParameterNames.STATE, state);
		}
		this.redirectStrategy.sendRedirect(request, response, uriBuilder.toUriString());
	}

	private void sendErrorResponse(HttpServletResponse response, OAuth2Error error) throws IOException {
		// TODO Send default html error response
		response.sendError(HttpStatus.BAD_REQUEST.value(), error.toString());
	}

	private static OAuth2Error createError(String errorCode, String parameterName) {
		return createError(errorCode, parameterName, "https://tools.ietf.org/html/rfc6749#section-4.1.2.1");
	}

	private static OAuth2Error createError(String errorCode, String parameterName, String errorUri) {
		return new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri);
	}

	private static boolean isPrincipalAuthenticated(Authentication principal) {
		return principal != null &&
				!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) &&
				principal.isAuthenticated();
	}

	private static class OAuth2AuthorizationRequestContext extends AbstractRequestContext {
		private final String responseType;
		private final String redirectUri;

		private OAuth2AuthorizationRequestContext(
				String authorizationUri, MultiValueMap parameters) {
			super(authorizationUri, parameters,
					parameters.getFirst(OAuth2ParameterNames.CLIENT_ID),
					parameters.getFirst(OAuth2ParameterNames.STATE),
					extractScopes(parameters));
			this.responseType = parameters.getFirst(OAuth2ParameterNames.RESPONSE_TYPE);
			this.redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
		}

		private static Set extractScopes(MultiValueMap parameters) {
			String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
			return StringUtils.hasText(scope) ?
					new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))) :
					Collections.emptySet();
		}

		private String getResponseType() {
			return this.responseType;
		}

		private String getRedirectUri() {
			return this.redirectUri;
		}

		protected String resolveRedirectUri() {
			return StringUtils.hasText(getRedirectUri()) ?
					getRedirectUri() :
					getRegisteredClient().getRedirectUris().iterator().next();
		}

		private OAuth2AuthorizationRequest buildAuthorizationRequest() {
			return OAuth2AuthorizationRequest.authorizationCode()
					.authorizationUri(getAuthorizationUri())
					.clientId(getClientId())
					.redirectUri(getRedirectUri())
					.scopes(getScopes())
					.state(getState())
					.additionalParameters(additionalParameters ->
							getParameters().entrySet().stream()
									.filter(e -> !e.getKey().equals(OAuth2ParameterNames.RESPONSE_TYPE) &&
											!e.getKey().equals(OAuth2ParameterNames.CLIENT_ID) &&
											!e.getKey().equals(OAuth2ParameterNames.REDIRECT_URI) &&
											!e.getKey().equals(OAuth2ParameterNames.SCOPE) &&
											!e.getKey().equals(OAuth2ParameterNames.STATE))
									.forEach(e -> additionalParameters.put(e.getKey(), e.getValue().get(0))))
					.build();
		}
	}

	private static class UserConsentRequestContext extends AbstractRequestContext {
		private OAuth2Authorization authorization;

		private UserConsentRequestContext(
				String authorizationUri, MultiValueMap parameters) {
			super(authorizationUri, parameters,
					parameters.getFirst(OAuth2ParameterNames.CLIENT_ID),
					parameters.getFirst(OAuth2ParameterNames.STATE),
					extractScopes(parameters));
		}

		private static Set extractScopes(MultiValueMap parameters) {
			List scope = parameters.get(OAuth2ParameterNames.SCOPE);
			return !CollectionUtils.isEmpty(scope) ? new HashSet<>(scope) : Collections.emptySet();
		}

		private OAuth2Authorization getAuthorization() {
			return this.authorization;
		}

		private void setAuthorization(OAuth2Authorization authorization) {
			this.authorization = authorization;
		}

		protected String resolveRedirectUri() {
			OAuth2AuthorizationRequest authorizationRequest = getAuthorizationRequest();
			return StringUtils.hasText(authorizationRequest.getRedirectUri()) ?
					authorizationRequest.getRedirectUri() :
					getRegisteredClient().getRedirectUris().iterator().next();
		}

		private OAuth2AuthorizationRequest getAuthorizationRequest() {
			return getAuthorization().getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
		}
	}

	private abstract static class AbstractRequestContext {
		private final String authorizationUri;
		private final MultiValueMap parameters;
		private final String clientId;
		private final String state;
		private final Set scopes;
		private RegisteredClient registeredClient;
		private OAuth2Error error;
		private boolean redirectOnError;

		protected AbstractRequestContext(String authorizationUri, MultiValueMap parameters,
				String clientId, String state, Set scopes) {
			this.authorizationUri = authorizationUri;
			this.parameters = parameters;
			this.clientId = clientId;
			this.state = state;
			this.scopes = scopes;
		}

		protected String getAuthorizationUri() {
			return this.authorizationUri;
		}

		protected MultiValueMap getParameters() {
			return this.parameters;
		}

		protected String getClientId() {
			return this.clientId;
		}

		protected String getState() {
			return this.state;
		}

		protected Set getScopes() {
			return this.scopes;
		}

		protected RegisteredClient getRegisteredClient() {
			return this.registeredClient;
		}

		protected void setRegisteredClient(RegisteredClient registeredClient) {
			this.registeredClient = registeredClient;
		}

		protected OAuth2Error getError() {
			return this.error;
		}

		protected void setError(OAuth2Error error) {
			this.error = error;
		}

		protected boolean hasError() {
			return getError() != null;
		}

		protected boolean isRedirectOnError() {
			return this.redirectOnError;
		}

		protected void setRedirectOnError(boolean redirectOnError) {
			this.redirectOnError = redirectOnError;
		}

		protected abstract String resolveRedirectUri();
	}

	private static class UserConsentPage {
		private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
		private static final String CONSENT_ACTION_PARAMETER_NAME = "consent_action";
		private static final String CONSENT_ACTION_APPROVE = "approve";
		private static final String CONSENT_ACTION_CANCEL = "cancel";

		private static void displayConsent(HttpServletRequest request, HttpServletResponse response,
				RegisteredClient registeredClient, OAuth2Authorization authorization) throws IOException {

			String consentPage = generateConsentPage(request, registeredClient, authorization);
			response.setContentType(TEXT_HTML_UTF8.toString());
			response.setContentLength(consentPage.getBytes(StandardCharsets.UTF_8).length);
			response.getWriter().write(consentPage);
		}

		private static boolean isConsentApproved(HttpServletRequest request) {
			return CONSENT_ACTION_APPROVE.equalsIgnoreCase(request.getParameter(CONSENT_ACTION_PARAMETER_NAME));
		}

		private static boolean isConsentCancelled(HttpServletRequest request) {
			return CONSENT_ACTION_CANCEL.equalsIgnoreCase(request.getParameter(CONSENT_ACTION_PARAMETER_NAME));
		}

		private static String generateConsentPage(HttpServletRequest request,
				RegisteredClient registeredClient, OAuth2Authorization authorization) {

			OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
					OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
			String state = authorization.getAttribute(
					OAuth2AuthorizationAttributeNames.STATE);

			StringBuilder builder = new StringBuilder();

			builder.append("");
			builder.append("");
			builder.append("");
			builder.append("    ");
			builder.append("    ");
			builder.append("    ");
			builder.append("    Consent required");
			builder.append("");
			builder.append("");
			builder.append("
"); builder.append("
"); builder.append("

Consent required

"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("

" + registeredClient.getClientId() + " wants to access your account " + authorization.getPrincipalName() + "

"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("

The following permissions are requested by the above app.
Please review these and consent if you approve.

"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append(" "); builder.append(" "); for (String scope : authorizationRequest.getScopes()) { builder.append("
"); builder.append(" "); builder.append(" "); builder.append("
"); } builder.append("
"); builder.append(" "); builder.append("
"); builder.append("
"); builder.append(" "); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append("

Your consent to provide access is required.
If you do not approve, click Cancel, in which case no information will be shared with the app.

"); builder.append("
"); builder.append("
"); builder.append("
"); builder.append(""); builder.append(""); return builder.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy