/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * 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.web.authentication;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

/**
 * Abstract processor of browser-based HTTP-based authentication requests.
 *
 *
Authentication Process

 *
 * The filter requires that you set the <tt>authenticationManager</tt> property. An
 * <tt>AuthenticationManager</tt> is required to process the authentication request tokens
 * created by implementing classes.
 *

 * This filter will intercept a request and attempt to perform authentication from that
 * request if the request matches the
 * {@link #setRequiresAuthenticationRequestMatcher(RequestMatcher)}.
 *

 * Authentication is performed by the
 * {@link #attemptAuthentication(HttpServletRequest, HttpServletResponse)
 * attemptAuthentication} method, which must be implemented by subclasses.
 *
 *
Authentication Success

 *
 * If authentication is successful, the resulting {@link Authentication} object will be
 * placed into the SecurityContext for the current thread, which is
 * guaranteed to have already been created by an earlier filter.
 *

 * The configured {@link #setAuthenticationSuccessHandler(AuthenticationSuccessHandler)
 * AuthenticationSuccessHandler} will then be called to take the redirect to the
 * appropriate destination after a successful login. The default behaviour is implemented
 * in a {@link SavedRequestAwareAuthenticationSuccessHandler} which will make use of any
 * <tt>DefaultSavedRequest</tt> set by the <tt>ExceptionTranslationFilter</tt> and
 * redirect the user to the URL contained therein. Otherwise it will redirect to the
 * webapp root "/". You can customize this behaviour by injecting a differently configured
 * instance of this class, or by using a different implementation.
 *

 * See the
 * {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)}
 * method for more information.
 *
 *
Authentication Failure

 *
 * If authentication fails, it will delegate to the configured
 * {@link AuthenticationFailureHandler} to allow the failure information to be conveyed to
 * the client. The default implementation is {@link SimpleUrlAuthenticationFailureHandler}
 * , which sends a 401 error code to the client. It may also be configured with a failure
 * URL as an alternative. Again you can inject whatever behaviour you require here.
 *
 *
Event Publication

 *
 * If authentication is successful, an {@link InteractiveAuthenticationSuccessEvent} will
 * be published via the application context. No events will be published if authentication
 * was unsuccessful, because this would generally be recorded via an
 * {@code AuthenticationManager}-specific application event.
 *
 *
Session Authentication

 *
 * The class has an optional {@link SessionAuthenticationStrategy} which will be invoked
 * immediately after a successful call to {@code attemptAuthentication()}. Different
 * implementations
 * {@link #setSessionAuthenticationStrategy(SessionAuthenticationStrategy) can be
 * injected} to enable things like session-fixation attack prevention or to control the
 * number of simultaneous sessions a principal may have.
 *
 * @author Ben Alex
 * @author Luke Taylor
 */
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware, MessageSourceAware {
	// ~ Static fields/initializers
	// =====================================================================================

	// ~ Instance fields
	// ================================================================================================

	protected ApplicationEventPublisher eventPublisher;
	protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
	private AuthenticationManager authenticationManager;
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private RememberMeServices rememberMeServices = new NullRememberMeServices();

	private RequestMatcher requiresAuthenticationRequestMatcher;

	private boolean continueChainBeforeSuccessfulAuthentication = false;

	private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();

	private boolean allowSessionCreation = true;

	private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
	private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

	// ~ Constructors
	// ===================================================================================================

	/**
	 * @param defaultFilterProcessesUrl the default value for <tt>filterProcessesUrl</tt>.
	 */
	protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
		setFilterProcessesUrl(defaultFilterProcessesUrl);
	}

	/**
	 * Creates a new instance
	 *
	 * @param requiresAuthenticationRequestMatcher the {@link RequestMatcher} used to
	 * determine if authentication is required. Cannot be null.
	 */
	protected AbstractAuthenticationProcessingFilter(
			RequestMatcher requiresAuthenticationRequestMatcher) {
		Assert.notNull(requiresAuthenticationRequestMatcher,
				"requiresAuthenticationRequestMatcher cannot be null");
		this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public void afterPropertiesSet() {
		Assert.notNull(authenticationManager, "authenticationManager must be specified");
	}

	/**
	 * Invokes the
	 * {@link #requiresAuthentication(HttpServletRequest, HttpServletResponse)
	 * requiresAuthentication} method to determine whether the request is for
	 * authentication and should be handled by this filter. If it is an authentication
	 * request, the
	 * {@link #attemptAuthentication(HttpServletRequest, HttpServletResponse)
	 * attemptAuthentication} will be invoked to perform the authentication. There are
	 * then three possible outcomes:
	 *

	 *
An <tt>Authentication</tt> object is returned. The configured
	 * {@link SessionAuthenticationStrategy} will be invoked (to handle any
	 * session-related behaviour such as creating a new session to protect against
	 * session-fixation attacks) followed by the invocation of
	 * {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)}
	 * method

	 *
An <tt>AuthenticationException</tt> occurs during authentication. The
	 * {@link #unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException)
	 * unsuccessfulAuthentication} method will be invoked

	 *
Null is returned, indicating that the authentication process is incomplete. The
	 * method will then return immediately, assuming that the subclass has done any
	 * necessary work (such as redirects) to continue the authentication process. The
	 * assumption is that a later request will be received by this method where the
	 * returned <tt>Authentication</tt> object is not null.
	 *

	 */
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);

			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Request is to process authentication");
		}

		Authentication authResult;

		try {
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				// authentication
				return;
			}
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
			logger.error(
					"An internal error occurred while trying to authenticate the user.",
					failed);
			unsuccessfulAuthentication(request, response, failed);

			return;
		}
		catch (AuthenticationException failed) {
			// Authentication failed
			unsuccessfulAuthentication(request, response, failed);

			return;
		}

		// Authentication success
		if (continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}

		successfulAuthentication(request, response, chain, authResult);
	}

	/**
	 * Indicates whether this filter should attempt to process a login request for the
	 * current invocation.
	 *

	 * It strips any parameters from the "path" section of the request URL (such as the
	 * jsessionid parameter in https://host/myapp/index.html;jsessionid=blah)
	 * before matching against the filterProcessesUrl property.
	 *

	 * Subclasses may override for special requirements, such as Tapestry integration.
	 *
	 * @return true if the filter should attempt authentication,
	 * false otherwise.
	 */
	protected boolean requiresAuthentication(HttpServletRequest request,
			HttpServletResponse response) {
		return requiresAuthenticationRequestMatcher.matches(request);
	}

	/**
	 * Performs actual authentication.
	 *

	 * The implementation should do one of the following:
	 *

	 *
Return a populated authentication token for the authenticated user, indicating
	 * successful authentication

	 *
Return null, indicating that the authentication process is still in progress.
	 * Before returning, the implementation should perform any additional work required to
	 * complete the process.

	 *
Throw an <tt>AuthenticationException</tt> if the authentication process fails

	 *

	 *
	 * @param request from which to extract parameters and perform the authentication
	 * @param response the response, which may be needed if the implementation has to do a
	 * redirect as part of a multi-stage authentication process (such as OpenID).
	 *
	 * @return the authenticated user token, or null if authentication is incomplete.
	 *
	 * @throws AuthenticationException if authentication fails.
	 */
	public abstract Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException, IOException,
			ServletException;

	/**
	 * Default behaviour for successful authentication.
	 *

	 *
Sets the successful <tt>Authentication</tt> object on the
	 * {@link SecurityContextHolder}

	 *
Informs the configured <tt>RememberMeServices</tt> of the successful login

	 *
Fires an {@link InteractiveAuthenticationSuccessEvent} via the configured
	 * <tt>ApplicationEventPublisher</tt>

	 *
Delegates additional behaviour to the {@link AuthenticationSuccessHandler}.

	 *

	 *
	 * Subclasses can override this method to continue the {@link FilterChain} after
	 * successful authentication.
	 * @param request
	 * @param response
	 * @param chain
	 * @param authResult the object returned from the <tt>attemptAuthentication</tt>
	 * method.
	 * @throws IOException
	 * @throws ServletException
	 */
	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, Authentication authResult)
			throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
					+ authResult);
		}

		SecurityContextHolder.getContext().setAuthentication(authResult);

		rememberMeServices.loginSuccess(request, response, authResult);

		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}

		successHandler.onAuthenticationSuccess(request, response, authResult);
	}

	/**
	 * Default behaviour for unsuccessful authentication.
	 *

	 *
Clears the {@link SecurityContextHolder}

	 *
Stores the exception in the session (if it exists or
	 * <tt>allowSesssionCreation</tt> is set to <tt>true</tt>)

	 *
Informs the configured <tt>RememberMeServices</tt> of the failed login

	 *
Delegates additional behaviour to the {@link AuthenticationFailureHandler}.

	 *

	 */
	protected void unsuccessfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException failed)
			throws IOException, ServletException {
		SecurityContextHolder.clearContext();

		if (logger.isDebugEnabled()) {
			logger.debug("Authentication request failed: " + failed.toString(), failed);
			logger.debug("Updated SecurityContextHolder to contain null Authentication");
			logger.debug("Delegating to authentication failure handler " + failureHandler);
		}

		rememberMeServices.loginFail(request, response);

		failureHandler.onAuthenticationFailure(request, response, failed);
	}

	protected AuthenticationManager getAuthenticationManager() {
		return authenticationManager;
	}

	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	/**
	 * Sets the URL that determines if authentication is required
	 *
	 * @param filterProcessesUrl
	 */
	public void setFilterProcessesUrl(String filterProcessesUrl) {
		setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(
				filterProcessesUrl));
	}

	public final void setRequiresAuthenticationRequestMatcher(
			RequestMatcher requestMatcher) {
		Assert.notNull(requestMatcher, "requestMatcher cannot be null");
		this.requiresAuthenticationRequestMatcher = requestMatcher;
	}

	public RememberMeServices getRememberMeServices() {
		return rememberMeServices;
	}

	public void setRememberMeServices(RememberMeServices rememberMeServices) {
		Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
		this.rememberMeServices = rememberMeServices;
	}

	/**
	 * Indicates if the filter chain should be continued prior to delegation to
	 * {@link #successfulAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, Authentication)}
	 * , which may be useful in certain environment (such as Tapestry applications).
	 * Defaults to false.
	 */
	public void setContinueChainBeforeSuccessfulAuthentication(
			boolean continueChainBeforeSuccessfulAuthentication) {
		this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
	}

	public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
		this.eventPublisher = eventPublisher;
	}

	public void setAuthenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		Assert.notNull(authenticationDetailsSource,
				"AuthenticationDetailsSource required");
		this.authenticationDetailsSource = authenticationDetailsSource;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	protected boolean getAllowSessionCreation() {
		return allowSessionCreation;
	}

	public void setAllowSessionCreation(boolean allowSessionCreation) {
		this.allowSessionCreation = allowSessionCreation;
	}

	/**
	 * The session handling strategy which will be invoked immediately after an
	 * authentication request is successfully processed by the
	 * <tt>AuthenticationManager</tt>. Used, for example, to handle changing of the
	 * session identifier to prevent session fixation attacks.
	 *
	 * @param sessionStrategy the implementation to use. If not set a null implementation
	 * is used.
	 */
	public void setSessionAuthenticationStrategy(
			SessionAuthenticationStrategy sessionStrategy) {
		this.sessionStrategy = sessionStrategy;
	}

	/**
	 * Sets the strategy used to handle a successful authentication. By default a
	 * {@link SavedRequestAwareAuthenticationSuccessHandler} is used.
	 */
	public void setAuthenticationSuccessHandler(
			AuthenticationSuccessHandler successHandler) {
		Assert.notNull(successHandler, "successHandler cannot be null");
		this.successHandler = successHandler;
	}

	public void setAuthenticationFailureHandler(
			AuthenticationFailureHandler failureHandler) {
		Assert.notNull(failureHandler, "failureHandler cannot be null");
		this.failureHandler = failureHandler;
	}

	protected AuthenticationSuccessHandler getSuccessHandler() {
		return successHandler;
	}

	protected AuthenticationFailureHandler getFailureHandler() {
		return failureHandler;
	}
}


/*
 * Copyright 2002-2012 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.config.http;

import org.w3c.dom.Element;

import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.ManagedMap;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfLogoutHandler;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
import org.springframework.security.web.csrf.MissingCsrfTokenException;
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
import org.springframework.security.web.session.InvalidSessionAccessDeniedHandler;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * Parser for the {@code CsrfFilter}.
 *
 * @author Rob Winch
 * @author Ankur Pathak
 * @since 3.2
 */
public class CsrfBeanDefinitionParser implements BeanDefinitionParser {

	private static final String REQUEST_DATA_VALUE_PROCESSOR = "requestDataValueProcessor";
	private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
	private static final String ATT_MATCHER = "request-matcher-ref";
	private static final String ATT_REPOSITORY = "token-repository-ref";

	private String csrfRepositoryRef;
	private BeanDefinition csrfFilter;

	@Override
	public BeanDefinition parse(Element element, ParserContext pc) {
		boolean disabled = element != null
				&& "true".equals(element.getAttribute("disabled"));
		if (disabled) {
			return null;
		}
		boolean webmvcPresent = ClassUtils.isPresent(DISPATCHER_SERVLET_CLASS_NAME,
				getClass().getClassLoader());
		if (webmvcPresent) {
			if (!pc.getRegistry().containsBeanDefinition(REQUEST_DATA_VALUE_PROCESSOR)) {
				RootBeanDefinition beanDefinition = new RootBeanDefinition(
						CsrfRequestDataValueProcessor.class);
				BeanComponentDefinition componentDefinition = new BeanComponentDefinition(
						beanDefinition, REQUEST_DATA_VALUE_PROCESSOR);
				pc.registerBeanComponent(componentDefinition);
			}
		}

		String matcherRef = null;
		if (element != null) {
			this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
			matcherRef = element.getAttribute(ATT_MATCHER);
		}

		if (!StringUtils.hasText(this.csrfRepositoryRef)) {

			RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(
					HttpSessionCsrfTokenRepository.class);
			BeanDefinitionBuilder lazyTokenRepository = BeanDefinitionBuilder
					.rootBeanDefinition(LazyCsrfTokenRepository.class);
			lazyTokenRepository.addConstructorArgValue(csrfTokenRepository);
			this.csrfRepositoryRef = pc.getReaderContext()
					.generateBeanName(lazyTokenRepository.getBeanDefinition());
			pc.registerBeanComponent(new BeanComponentDefinition(
					lazyTokenRepository.getBeanDefinition(), this.csrfRepositoryRef));
		}

		BeanDefinitionBuilder builder = BeanDefinitionBuilder
				.rootBeanDefinition(CsrfFilter.class);
		builder.addConstructorArgReference(this.csrfRepositoryRef);

		if (StringUtils.hasText(matcherRef)) {
			builder.addPropertyReference("requireCsrfProtectionMatcher", matcherRef);
		}

		this.csrfFilter = builder.getBeanDefinition();
		return this.csrfFilter;
	}

	/**
	 * Populate the AccessDeniedHandler on the {@link CsrfFilter}
	 *
	 * @param invalidSessionStrategy the {@link InvalidSessionStrategy} to use
	 * @param defaultDeniedHandler the {@link AccessDeniedHandler} to use
	 */
	void initAccessDeniedHandler(BeanDefinition invalidSessionStrategy,
			BeanMetadataElement defaultDeniedHandler) {
		BeanMetadataElement accessDeniedHandler = createAccessDeniedHandler(
				invalidSessionStrategy, defaultDeniedHandler);
		this.csrfFilter.getPropertyValues().addPropertyValue("accessDeniedHandler",
				accessDeniedHandler);
	}

	/**
	 * Creates the {@link AccessDeniedHandler} from the result of
	 * {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)} and
	 * {@link #getInvalidSessionStrategy(HttpSecurityBuilder)}. If
	 * {@link #getInvalidSessionStrategy(HttpSecurityBuilder)} is non-null, then a
	 * {@link DelegatingAccessDeniedHandler} is used in combination with
	 * {@link InvalidSessionAccessDeniedHandler} and the
	 * {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)}. Otherwise, only
	 * {@link #getDefaultAccessDeniedHandler(HttpSecurityBuilder)} is used.
	 *
	 * @param invalidSessionStrategy the {@link InvalidSessionStrategy} to use
	 * @param defaultDeniedHandler the {@link AccessDeniedHandler} to use
	 *
	 * @return the {@link BeanMetadataElement} that is the {@link AccessDeniedHandler} to
	 * populate on the {@link CsrfFilter}
	 */
	private BeanMetadataElement createAccessDeniedHandler(
			BeanDefinition invalidSessionStrategy,
			BeanMetadataElement defaultDeniedHandler) {
		if (invalidSessionStrategy == null) {
			return defaultDeniedHandler;
		}
		ManagedMap<Class<? extends AccessDeniedException>, BeanDefinition> handlers = new ManagedMap<Class<? extends AccessDeniedException>, BeanDefinition>();
		BeanDefinitionBuilder invalidSessionHandlerBldr = BeanDefinitionBuilder
				.rootBeanDefinition(InvalidSessionAccessDeniedHandler.class);
		invalidSessionHandlerBldr.addConstructorArgValue(invalidSessionStrategy);
		handlers.put(MissingCsrfTokenException.class,
				invalidSessionHandlerBldr.getBeanDefinition());

		BeanDefinitionBuilder deniedBldr = BeanDefinitionBuilder
				.rootBeanDefinition(DelegatingAccessDeniedHandler.class);
		deniedBldr.addConstructorArgValue(handlers);
		deniedBldr.addConstructorArgValue(defaultDeniedHandler);

		return deniedBldr.getBeanDefinition();
	}

	BeanDefinition getCsrfAuthenticationStrategy() {
		BeanDefinitionBuilder csrfAuthenticationStrategy = BeanDefinitionBuilder
				.rootBeanDefinition(CsrfAuthenticationStrategy.class);
		csrfAuthenticationStrategy.addConstructorArgReference(this.csrfRepositoryRef);
		return csrfAuthenticationStrategy.getBeanDefinition();
	}

	BeanDefinition getCsrfLogoutHandler() {
		BeanDefinitionBuilder csrfAuthenticationStrategy = BeanDefinitionBuilder
				.rootBeanDefinition(CsrfLogoutHandler.class);
		csrfAuthenticationStrategy.addConstructorArgReference(this.csrfRepositoryRef);
		return csrfAuthenticationStrategy.getBeanDefinition();
	}
}


/*
 * Copyright 2011-2016 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.crypto.encrypt;

import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

/**
 * Static helper for working with the Cipher API.
 * @author Keith Donald
 */
class CipherUtils {

	/**
	 * Generates a SecretKey.
	 */
	public static SecretKey newSecretKey(String algorithm, String password) {
		return newSecretKey(algorithm, new PBEKeySpec(password.toCharArray()));
	}

	/**
	 * Generates a SecretKey.
	 */
	public static SecretKey newSecretKey(String algorithm, PBEKeySpec keySpec) {
		try {
			SecretKeyFactory factory = SecretKeyFactory.getInstance(algorithm);
			return factory.generateSecret(keySpec);
		}
		catch (NoSuchAlgorithmException e) {
			throw new IllegalArgumentException("Not a valid encryption algorithm", e);
		}
		catch (InvalidKeySpecException e) {
			throw new IllegalArgumentException("Not a valid secret key", e);
		}
	}

	/**
	 * Constructs a new Cipher.
	 */
	public static Cipher newCipher(String algorithm) {
		try {
			return Cipher.getInstance(algorithm);
		}
		catch (NoSuchAlgorithmException e) {
			throw new IllegalArgumentException("Not a valid encryption algorithm", e);
		}
		catch (NoSuchPaddingException e) {
			throw new IllegalStateException("Should not happen", e);
		}
	}

	/**
	 * Initializes the Cipher for use.
	 */
	public static <T extends AlgorithmParameterSpec> T getParameterSpec(Cipher cipher,
			Class<T> parameterSpecClass) {
		try {
			return cipher.getParameters().getParameterSpec(parameterSpecClass);
		}
		catch (InvalidParameterSpecException e) {
			throw new IllegalArgumentException("Unable to access parameter", e);
		}
	}

	/**
	 * Initializes the Cipher for use.
	 */
	public static void initCipher(Cipher cipher, int mode, SecretKey secretKey) {
		initCipher(cipher, mode, secretKey, null);
	}

	/**
	 * Initializes the Cipher for use.
	 */
	public static void initCipher(Cipher cipher, int mode, SecretKey secretKey,
			byte[] salt, int iterationCount) {
		initCipher(cipher, mode, secretKey, new PBEParameterSpec(salt, iterationCount));
	}

	/**
	 * Initializes the Cipher for use.
	 */
	public static void initCipher(Cipher cipher, int mode, SecretKey secretKey,
			AlgorithmParameterSpec parameterSpec) {
		try {
			if (parameterSpec != null) {
				cipher.init(mode, secretKey, parameterSpec);
			}
			else {
				cipher.init(mode, secretKey);
			}
		}
		catch (InvalidKeyException e) {
			throw new IllegalArgumentException(
					"Unable to initialize due to invalid secret key", e);
		}
		catch (InvalidAlgorithmParameterException e) {
			throw new IllegalStateException(
					"Unable to initialize due to invalid decryption parameter spec", e);
		}
	}

	/**
	 * Invokes the Cipher to perform encryption or decryption (depending on the
	 * initialized mode).
	 */
	public static byte[] doFinal(Cipher cipher, byte[] input) {
		try {
			return cipher.doFinal(input);
		}
		catch (IllegalBlockSizeException e) {
			throw new IllegalStateException(
					"Unable to invoke Cipher due to illegal block size", e);
		}
		catch (BadPaddingException e) {
			throw new IllegalStateException("Unable to invoke Cipher due to bad padding",
					e);
		}
	}

	private CipherUtils() {
	}

}


/*
 * Copyright 2002-2018 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.web.authentication.preauth;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.*;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

/**
 * Base class for processing filters that handle pre-authenticated authentication
 * requests, where it is assumed that the principal has already been authenticated by an
 * external system.
 *

 * The purpose is then only to extract the necessary information on the principal from the
 * incoming request, rather than to authenticate them. External authentication systems may
 * provide this information via request data such as headers or cookies which the
 * pre-authentication system can extract. It is assumed that the external system is
 * responsible for the accuracy of the data and preventing the submission of forged
 * values.
 *
 * Subclasses must implement the {@code getPreAuthenticatedPrincipal()} and
 * {@code getPreAuthenticatedCredentials()} methods. Subclasses of this filter are
 * typically used in combination with a {@code PreAuthenticatedAuthenticationProvider},
 * which is used to load additional data for the user. This provider will reject null
 * credentials, so the {@link #getPreAuthenticatedCredentials} method should not return
 * null for a valid principal.
 *

 * If the security context already contains an {@code Authentication} object (either from
 * a invocation of the filter or because of some other authentication mechanism), the
 * filter will do nothing by default. You can force it to check for a change in the
 * principal by setting the {@link #setCheckForPrincipalChanges(boolean)
 * checkForPrincipalChanges} property.
 *

 * By default, the filter chain will proceed when an authentication attempt fails in order
 * to allow other authentication mechanisms to process the request. To reject the
 * credentials immediately, set the
 * <tt>continueFilterChainOnUnsuccessfulAuthentication</tt> flag to false. The exception
 * raised by the <tt>AuthenticationManager</tt> will the be re-thrown. Note that this will
 * not affect cases where the principal returned by {@link #getPreAuthenticatedPrincipal}
 * is null, when the chain will still proceed as normal.
 *
 * @author Luke Taylor
 * @author Ruud Senden
 * @author Rob Winch
 * @since 2.0
 */
public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFilterBean
		implements ApplicationEventPublisherAware {

	private ApplicationEventPublisher eventPublisher = null;
	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
	private AuthenticationManager authenticationManager = null;
	private boolean continueFilterChainOnUnsuccessfulAuthentication = true;
	private boolean checkForPrincipalChanges;
	private boolean invalidateSessionOnPrincipalChange = true;
	private AuthenticationSuccessHandler authenticationSuccessHandler = null;
	private AuthenticationFailureHandler authenticationFailureHandler = null;

	/**
	 * Check whether all required properties have been set.
	 */
	@Override
	public void afterPropertiesSet() {
		try {
			super.afterPropertiesSet();
		}
		catch (ServletException e) {
			// convert to RuntimeException for passivity on afterPropertiesSet signature
			throw new RuntimeException(e);
		}
		Assert.notNull(authenticationManager, "An AuthenticationManager must be set");
	}

	/**
	 * Try to authenticate a pre-authenticated user with Spring Security if the user has
	 * not yet been authenticated.
	 */
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {

		if (logger.isDebugEnabled()) {
			logger.debug("Checking secure context token: "
					+ SecurityContextHolder.getContext().getAuthentication());
		}

		if (requiresAuthentication((HttpServletRequest) request)) {
			doAuthenticate((HttpServletRequest) request, (HttpServletResponse) response);
		}

		chain.doFilter(request, response);
	}

	/**
	 * Determines if the current principal has changed. The default implementation tries
	 *
	 *

	 *
If the {@link #getPreAuthenticatedPrincipal(HttpServletRequest)} is a String, the {@link Authentication#getName()} is compared against the pre authenticated principal

	 *
Otherwise, the {@link #getPreAuthenticatedPrincipal(HttpServletRequest)} is compared against the {@link Authentication#getPrincipal()}
	 *

	 *
	 *

	 * Subclasses can override this method to determine when a principal has changed.
	 *

	 *
	 * @param request
	 * @param currentAuthentication
	 * @return true if the principal has changed, else false
	 */
	protected boolean principalChanged(HttpServletRequest request, Authentication currentAuthentication) {

		Object principal = getPreAuthenticatedPrincipal(request);

		if ((principal instanceof String) && currentAuthentication.getName().equals(principal)) {
			return false;
		}

		if (principal != null && principal.equals(currentAuthentication.getPrincipal())) {
			return false;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Pre-authenticated principal has changed to " + principal + " and will be reauthenticated");
		}
		return true;
	}

	/**
	 * Do the actual authentication for a pre-authenticated user.
	 */
	private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
		Authentication authResult;

		Object principal = getPreAuthenticatedPrincipal(request);
		Object credentials = getPreAuthenticatedCredentials(request);

		if (principal == null) {
			if (logger.isDebugEnabled()) {
				logger.debug("No pre-authenticated principal found in request");
			}

			return;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("preAuthenticatedPrincipal = " + principal
					+ ", trying to authenticate");
		}

		try {
			PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(
					principal, credentials);
			authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
			authResult = authenticationManager.authenticate(authRequest);
			successfulAuthentication(request, response, authResult);
		}
		catch (AuthenticationException failed) {
			unsuccessfulAuthentication(request, response, failed);

			if (!continueFilterChainOnUnsuccessfulAuthentication) {
				throw failed;
			}
		}
	}

	private boolean requiresAuthentication(HttpServletRequest request) {
		Authentication currentUser = SecurityContextHolder.getContext()
				.getAuthentication();

		if (currentUser == null) {
			return true;
		}

		if (!checkForPrincipalChanges) {
			return false;
		}

		if (!principalChanged(request, currentUser)) {
			return false;
		}

		logger.debug("Pre-authenticated principal has changed and will be reauthenticated");

		if (invalidateSessionOnPrincipalChange) {
			SecurityContextHolder.clearContext();

			HttpSession session = request.getSession(false);

			if (session != null) {
				logger.debug("Invalidating existing session");
				session.invalidate();
				request.getSession();
			}
		}

		return true;
	}

	/**
	 * Puts the Authentication instance returned by the authentication
	 * manager into the secure context.
	 */
	protected void successfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, Authentication authResult) throws IOException, ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Authentication success: " + authResult);
		}
		SecurityContextHolder.getContext().setAuthentication(authResult);
		// Fire event
		if (this.eventPublisher != null) {
			eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
					authResult, this.getClass()));
		}

		if (authenticationSuccessHandler != null) {
			authenticationSuccessHandler.onAuthenticationSuccess(request, response, authResult);
		}
	}

	/**
	 * Ensures the authentication object in the secure context is set to null when
	 * authentication fails.
	 *

	 * Caches the failure exception as a request attribute
	 */
	protected void unsuccessfulAuthentication(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
		SecurityContextHolder.clearContext();

		if (logger.isDebugEnabled()) {
			logger.debug("Cleared security context due to exception", failed);
		}
		request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, failed);

		if (authenticationFailureHandler != null) {
			authenticationFailureHandler.onAuthenticationFailure(request, response, failed);
		}
	}

	/**
	 * @param anApplicationEventPublisher The ApplicationEventPublisher to use
	 */
	public void setApplicationEventPublisher(
			ApplicationEventPublisher anApplicationEventPublisher) {
		this.eventPublisher = anApplicationEventPublisher;
	}

	/**
	 * @param authenticationDetailsSource The AuthenticationDetailsSource to use
	 */
	public void setAuthenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		Assert.notNull(authenticationDetailsSource,
				"AuthenticationDetailsSource required");
		this.authenticationDetailsSource = authenticationDetailsSource;
	}

	protected AuthenticationDetailsSource<HttpServletRequest, ?> getAuthenticationDetailsSource() {
		return authenticationDetailsSource;
	}

	/**
	 * @param authenticationManager The AuthenticationManager to use
	 */
	public void setAuthenticationManager(AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	/**
	 * If set to {@code true}, any {@code AuthenticationException} raised by the
	 * {@code AuthenticationManager} will be swallowed, and the request will be allowed to
	 * proceed, potentially using alternative authentication mechanisms. If {@code false}
	 * (the default), authentication failure will result in an immediate exception.
	 *
	 * @param shouldContinue set to {@code true} to allow the request to proceed after a
	 * failed authentication.
	 */
	public void setContinueFilterChainOnUnsuccessfulAuthentication(boolean shouldContinue) {
		continueFilterChainOnUnsuccessfulAuthentication = shouldContinue;
	}

	/**
	 * If set, the pre-authenticated principal will be checked on each request and
	 * compared against the name of the current <tt>Authentication</tt> object. A check to
	 * determine if {@link Authentication#getPrincipal()} is equal to the principal will
	 * also be performed. If a change is detected, the user will be reauthenticated.
	 *
	 * @param checkForPrincipalChanges
	 */
	public void setCheckForPrincipalChanges(boolean checkForPrincipalChanges) {
		this.checkForPrincipalChanges = checkForPrincipalChanges;
	}

	/**
	 * If <tt>checkForPrincipalChanges</tt> is set, and a change of principal is detected,
	 * determines whether any existing session should be invalidated before proceeding to
	 * authenticate the new principal.
	 *
	 * @param invalidateSessionOnPrincipalChange <tt>false</tt> to retain the existing
	 * session. Defaults to <tt>true</tt>.
	 */
	public void setInvalidateSessionOnPrincipalChange(
			boolean invalidateSessionOnPrincipalChange) {
		this.invalidateSessionOnPrincipalChange = invalidateSessionOnPrincipalChange;
	}

	/**
	 * Sets the strategy used to handle a successful authentication.
	 */
	public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
		this.authenticationSuccessHandler = authenticationSuccessHandler;
	}

	/**
	 * Sets the strategy used to handle a failed authentication.
	 */
	public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
		this.authenticationFailureHandler = authenticationFailureHandler;
	}

	/**
	 * Override to extract the principal information from the current request
	 */
	protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);

	/**
	 * Override to extract the credentials (if applicable) from the current request.
	 * Should not return null for a valid principal, though some implementations may
	 * return a dummy value.
	 */
	protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
}


/*
 * Copyright 2002-2016 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.web.util;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Locale;

import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

/**
 * Base class for response wrappers which encapsulate the logic for handling an event when
 * the {@link javax.servlet.http.HttpServletResponse} is committed.
 *
 * @since 4.0.2
 * @author Rob Winch
 */
public abstract class OnCommittedResponseWrapper extends HttpServletResponseWrapper {

	private boolean disableOnCommitted;

	/**
	 * The Content-Length response header. If this is greater than 0, then once
	 * {@link #contentWritten} is larger than or equal the response is considered
	 * committed.
	 */
	private long contentLength;

	/**
	 * The size of data written to the response body. The field will only be updated when
	 * {@link #disableOnCommitted} is false.
	 */
	private long contentWritten;

	/**
	 * @param response the response to be wrapped
	 */
	public OnCommittedResponseWrapper(HttpServletResponse response) {
		super(response);
	}

	@Override
	public void addHeader(String name, String value) {
		if ("Content-Length".equalsIgnoreCase(name)) {
			setContentLength(Long.parseLong(value));
		}
		super.addHeader(name, value);
	}

	@Override
	public void setContentLength(int len) {
		setContentLength((long) len);
		super.setContentLength(len);
	}

	private void setContentLength(long len) {
		this.contentLength = len;
		checkContentLength(0);
	}

	/**
	 * Invoke this method to disable invoking
	 * {@link OnCommittedResponseWrapper#onResponseCommitted()} when the
	 * {@link javax.servlet.http.HttpServletResponse} is committed. This can be useful in
	 * the event that Async Web Requests are made.
	 */
	protected void disableOnResponseCommitted() {
		this.disableOnCommitted = true;
	}

	/**
	 * Returns true if {@link #onResponseCommitted()} will be invoked when the response is
	 * committed, else false.
	 * @return if {@link #onResponseCommitted()} is enabled
	 */
	protected boolean isDisableOnResponseCommitted() {
		return this.disableOnCommitted;
	}

	/**
	 * Implement the logic for handling the {@link javax.servlet.http.HttpServletResponse}
	 * being committed
	 */
	protected abstract void onResponseCommitted();

	/**
	 * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
	 * before calling the superclass sendError()
	 */
	@Override
	public final void sendError(int sc) throws IOException {
		doOnResponseCommitted();
		super.sendError(sc);
	}

	/**
	 * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
	 * before calling the superclass sendError()
	 */
	@Override
	public final void sendError(int sc, String msg) throws IOException {
		doOnResponseCommitted();
		super.sendError(sc, msg);
	}

	/**
	 * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
	 * before calling the superclass sendRedirect()
	 */
	@Override
	public final void sendRedirect(String location) throws IOException {
		doOnResponseCommitted();
		super.sendRedirect(location);
	}

	/**
	 * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
	 * before calling the calling getOutputStream().close() or
	 * getOutputStream().flush()
	 */
	@Override
	public ServletOutputStream getOutputStream() throws IOException {
		return new SaveContextServletOutputStream(super.getOutputStream());
	}

	/**
	 * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
	 * before calling the getWriter().close() or
	 * getWriter().flush()
	 */
	@Override
	public PrintWriter getWriter() throws IOException {
		return new SaveContextPrintWriter(super.getWriter());
	}

	/**
	 * Makes sure {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked
	 * before calling the superclass flushBuffer()
	 */
	@Override
	public void flushBuffer() throws IOException {
		doOnResponseCommitted();
		super.flushBuffer();
	}

	private void trackContentLength(boolean content) {
		if (!this.disableOnCommitted) {
			checkContentLength(content ? 4 : 5); // TODO Localization
		}
	}

	private void trackContentLength(char content) {
		if (!this.disableOnCommitted) {
			checkContentLength(1);
		}
	}

	private void trackContentLength(Object content) {
		if (!this.disableOnCommitted) {
			trackContentLength(String.valueOf(content));
		}
	}

	private void trackContentLength(byte[] content) {
		if (!this.disableOnCommitted) {
			checkContentLength(content == null ? 0 : content.length);
		}
	}

	private void trackContentLength(char[] content) {
		if (!this.disableOnCommitted) {
			checkContentLength(content == null ? 0 : content.length);
		}
	}

	private void trackContentLength(int content) {
		if (!this.disableOnCommitted) {
			trackContentLength(String.valueOf(content));
		}
	}

	private void trackContentLength(float content) {
		if (!this.disableOnCommitted) {
			trackContentLength(String.valueOf(content));
		}
	}

	private void trackContentLength(double content) {
		if (!this.disableOnCommitted) {
			trackContentLength(String.valueOf(content));
		}
	}

	private void trackContentLengthLn() {
		if (!this.disableOnCommitted) {
			trackContentLength("\r\n");
		}
	}

	private void trackContentLength(String content) {
		if (!this.disableOnCommitted) {
			int contentLength = content == null ? 4 : content.length();
			checkContentLength(contentLength);
		}
	}

	/**
	 * Adds the contentLengthToWrite to the total contentWritten size and checks to see if
	 * the response should be written.
	 *
	 * @param contentLengthToWrite the size of the content that is about to be written.
	 */
	private void checkContentLength(long contentLengthToWrite) {
		this.contentWritten += contentLengthToWrite;
		boolean isBodyFullyWritten = this.contentLength > 0
				&& this.contentWritten >= this.contentLength;
		int bufferSize = getBufferSize();
		boolean requiresFlush = bufferSize > 0 && this.contentWritten >= bufferSize;
		if (isBodyFullyWritten || requiresFlush) {
			doOnResponseCommitted();
		}
	}

	/**
	 * Calls onResponseCommmitted() with the current contents as long as
	 * {@link #disableOnResponseCommitted()} was not invoked.
	 */
	private void doOnResponseCommitted() {
		if (!this.disableOnCommitted) {
			onResponseCommitted();
			disableOnResponseCommitted();
		}
	}

	/**
	 * Ensures {@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
	 * calling the prior to methods that commit the response. We delegate all methods to
	 * the original {@link java.io.PrintWriter} to ensure that the behavior is as close to
	 * the original {@link java.io.PrintWriter} as possible. See SEC-2039
	 * @author Rob Winch
	 */
	private class SaveContextPrintWriter extends PrintWriter {
		private final PrintWriter delegate;

		public SaveContextPrintWriter(PrintWriter delegate) {
			super(delegate);
			this.delegate = delegate;
		}

		@Override
		public void flush() {
			doOnResponseCommitted();
			this.delegate.flush();
		}

		@Override
		public void close() {
			doOnResponseCommitted();
			this.delegate.close();
		}

		@Override
		public int hashCode() {
			return this.delegate.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			return this.delegate.equals(obj);
		}

		@Override
		public String toString() {
			return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
		}

		@Override
		public boolean checkError() {
			return this.delegate.checkError();
		}

		@Override
		public void write(int c) {
			trackContentLength(c);
			this.delegate.write(c);
		}

		@Override
		public void write(char[] buf, int off, int len) {
			checkContentLength(len);
			this.delegate.write(buf, off, len);
		}

		@Override
		public void write(char[] buf) {
			trackContentLength(buf);
			this.delegate.write(buf);
		}

		@Override
		public void write(String s, int off, int len) {
			checkContentLength(len);
			this.delegate.write(s, off, len);
		}

		@Override
		public void write(String s) {
			trackContentLength(s);
			this.delegate.write(s);
		}

		@Override
		public void print(boolean b) {
			trackContentLength(b);
			this.delegate.print(b);
		}

		@Override
		public void print(char c) {
			trackContentLength(c);
			this.delegate.print(c);
		}

		@Override
		public void print(int i) {
			trackContentLength(i);
			this.delegate.print(i);
		}

		@Override
		public void print(long l) {
			trackContentLength(l);
			this.delegate.print(l);
		}

		@Override
		public void print(float f) {
			trackContentLength(f);
			this.delegate.print(f);
		}

		@Override
		public void print(double d) {
			trackContentLength(d);
			this.delegate.print(d);
		}

		@Override
		public void print(char[] s) {
			trackContentLength(s);
			this.delegate.print(s);
		}

		@Override
		public void print(String s) {
			trackContentLength(s);
			this.delegate.print(s);
		}

		@Override
		public void print(Object obj) {
			trackContentLength(obj);
			this.delegate.print(obj);
		}

		@Override
		public void println() {
			trackContentLengthLn();
			this.delegate.println();
		}

		@Override
		public void println(boolean x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(char x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(int x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(long x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(float x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(double x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(char[] x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(String x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public void println(Object x) {
			trackContentLength(x);
			trackContentLengthLn();
			this.delegate.println(x);
		}

		@Override
		public PrintWriter printf(String format, Object... args) {
			return this.delegate.printf(format, args);
		}

		@Override
		public PrintWriter printf(Locale l, String format, Object... args) {
			return this.delegate.printf(l, format, args);
		}

		@Override
		public PrintWriter format(String format, Object... args) {
			return this.delegate.format(format, args);
		}

		@Override
		public PrintWriter format(Locale l, String format, Object... args) {
			return this.delegate.format(l, format, args);
		}

		@Override
		public PrintWriter append(CharSequence csq) {
			checkContentLength(csq.length());
			return this.delegate.append(csq);
		}

		@Override
		public PrintWriter append(CharSequence csq, int start, int end) {
			checkContentLength(end - start);
			return this.delegate.append(csq, start, end);
		}

		@Override
		public PrintWriter append(char c) {
			trackContentLength(c);
			return this.delegate.append(c);
		}
	}

	/**
	 * Ensures{@link OnCommittedResponseWrapper#onResponseCommitted()} is invoked before
	 * calling methods that commit the response. We delegate all methods to the original
	 * {@link javax.servlet.ServletOutputStream} to ensure that the behavior is as close
	 * to the original {@link javax.servlet.ServletOutputStream} as possible. See SEC-2039
	 *
	 * @author Rob Winch
	 */
	private class SaveContextServletOutputStream extends ServletOutputStream {
		private final ServletOutputStream delegate;

		public SaveContextServletOutputStream(ServletOutputStream delegate) {
			this.delegate = delegate;
		}

		@Override
		public void write(int b) throws IOException {
			trackContentLength(b);
			this.delegate.write(b);
		}

		@Override
		public void flush() throws IOException {
			doOnResponseCommitted();
			this.delegate.flush();
		}

		@Override
		public void close() throws IOException {
			doOnResponseCommitted();
			this.delegate.close();
		}

		@Override
		public int hashCode() {
			return this.delegate.hashCode();
		}

		@Override
		public boolean equals(Object obj) {
			return this.delegate.equals(obj);
		}

		@Override
		public void print(boolean b) throws IOException {
			trackContentLength(b);
			this.delegate.print(b);
		}

		@Override
		public void print(char c) throws IOException {
			trackContentLength(c);
			this.delegate.print(c);
		}

		@Override
		public void print(double d) throws IOException {
			trackContentLength(d);
			this.delegate.print(d);
		}

		@Override
		public void print(float f) throws IOException {
			trackContentLength(f);
			this.delegate.print(f);
		}

		@Override
		public void print(int i) throws IOException {
			trackContentLength(i);
			this.delegate.print(i);
		}

		@Override
		public void print(long l) throws IOException {
			trackContentLength(l);
			this.delegate.print(l);
		}

		@Override
		public void print(String s) throws IOException {
			trackContentLength(s);
			this.delegate.print(s);
		}

		@Override
		public void println() throws IOException {
			trackContentLengthLn();
			this.delegate.println();
		}

		@Override
		public void println(boolean b) throws IOException {
			trackContentLength(b);
			trackContentLengthLn();
			this.delegate.println(b);
		}

		@Override
		public void println(char c) throws IOException {
			trackContentLength(c);
			trackContentLengthLn();
			this.delegate.println(c);
		}

		@Override
		public void println(double d) throws IOException {
			trackContentLength(d);
			trackContentLengthLn();
			this.delegate.println(d);
		}

		@Override
		public void println(float f) throws IOException {
			trackContentLength(f);
			trackContentLengthLn();
			this.delegate.println(f);
		}

		@Override
		public void println(int i) throws IOException {
			trackContentLength(i);
			trackContentLengthLn();
			this.delegate.println(i);
		}

		@Override
		public void println(long l) throws IOException {
			trackContentLength(l);
			trackContentLengthLn();
			this.delegate.println(l);
		}

		@Override
		public void println(String s) throws IOException {
			trackContentLength(s);
			trackContentLengthLn();
			this.delegate.println(s);
		}

		@Override
		public void write(byte[] b) throws IOException {
			trackContentLength(b);
			this.delegate.write(b);
		}

		@Override
		public void write(byte[] b, int off, int len) throws IOException {
			checkContentLength(len);
			this.delegate.write(b, off, len);
		}

		public boolean isReady() {
			return this.delegate.isReady();
		}

		public void setWriteListener(WriteListener writeListener) {
			this.delegate.setWriteListener(writeListener);
		}

		@Override
		public String toString() {
			return getClass().getName() + "[delegate=" + this.delegate.toString() + "]";
		}
	}
}


/*
 * Copyright 2002-2016 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.access.expression.method;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParseException;
import org.springframework.security.access.prepost.PostInvocationAttribute;
import org.springframework.security.access.prepost.PreInvocationAttribute;
import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory;

/**
 * {@link PrePostInvocationAttributeFactory} which interprets the annotation value as an
 * expression to be evaluated at runtime.
 *
 * @author Luke Taylor
 * @author Rob Winch
 * @since 3.0
 */
public class ExpressionBasedAnnotationAttributeFactory implements
		PrePostInvocationAttributeFactory {
	private final Object parserLock = new Object();
	private ExpressionParser parser;
	private MethodSecurityExpressionHandler handler;

	public ExpressionBasedAnnotationAttributeFactory(
			MethodSecurityExpressionHandler handler) {
		this.handler = handler;
	}

	public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute,
			String filterObject, String preAuthorizeAttribute) {
		try {
			// TODO: Optimization of permitAll
			ExpressionParser parser = getParser();
			Expression preAuthorizeExpression = preAuthorizeAttribute == null ? parser
					.parseExpression("permitAll") : parser
					.parseExpression(preAuthorizeAttribute);
			Expression preFilterExpression = preFilterAttribute == null ? null : parser
					.parseExpression(preFilterAttribute);
			return new PreInvocationExpressionAttribute(preFilterExpression,
					filterObject, preAuthorizeExpression);
		}
		catch (ParseException e) {
			throw new IllegalArgumentException("Failed to parse expression '"
					+ e.getExpressionString() + "'", e);
		}
	}

	public PostInvocationAttribute createPostInvocationAttribute(
			String postFilterAttribute, String postAuthorizeAttribute) {
		try {
			ExpressionParser parser = getParser();
			Expression postAuthorizeExpression = postAuthorizeAttribute == null ? null
					: parser.parseExpression(postAuthorizeAttribute);
			Expression postFilterExpression = postFilterAttribute == null ? null : parser
					.parseExpression(postFilterAttribute);

			if (postFilterExpression != null || postAuthorizeExpression != null) {
				return new PostInvocationExpressionAttribute(postFilterExpression,
						postAuthorizeExpression);
			}
		}
		catch (ParseException e) {
			throw new IllegalArgumentException("Failed to parse expression '"
					+ e.getExpressionString() + "'", e);
		}

		return null;
	}

	/**
	 * Delay the lookup of the {@link ExpressionParser} to prevent SEC-2136
	 *
	 * @return
	 */
	private ExpressionParser getParser() {
		if (this.parser != null) {
			return this.parser;
		}
		synchronized (parserLock) {
			this.parser = handler.getExpressionParser();
			this.handler = null;
		}
		return this.parser;
	}
}


/*
 * Copyright 2012-2017 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.web.firewall;

import org.springframework.http.HttpMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 *

 * A strict implementation of {@link HttpFirewall} that rejects any suspicious requests
 * with a {@link RequestRejectedException}.
 *

 *

 * The following rules are applied to the firewall:
 *

 *

 *

 * Rejects HTTP methods that are not allowed. This specified to block
 * HTTP Verb tampering and XST attacks.
 * See {@link #setAllowedHttpMethods(Collection)}
 *

 *

 * Rejects URLs that are not normalized to avoid bypassing security constraints. There is
 * no way to disable this as it is considered extremely risky to disable this constraint.
 * A few options to allow this behavior is to normalize the request prior to the firewall
 * or using {@link DefaultHttpFirewall} instead. Please keep in mind that normalizing the
 * request is fragile and why requests are rejected rather than normalized.
 *

 *

 * Rejects URLs that contain characters that are not printable ASCII characters. There is
 * no way to disable this as it is considered extremely risky to disable this constraint.
 *

 *

 * Rejects URLs that contain semicolons. See {@link #setAllowSemicolon(boolean)}
 *

 *

 * Rejects URLs that contain a URL encoded slash. See
 * {@link #setAllowUrlEncodedSlash(boolean)}
 *

 *

 * Rejects URLs that contain a backslash. See {@link #setAllowBackSlash(boolean)}
 *

 *

 * Rejects URLs that contain a URL encoded percent. See
 * {@link #setAllowUrlEncodedPercent(boolean)}
 *

 *

 *
 * @see DefaultHttpFirewall
 * @author Rob Winch
 * @since 4.2.4
 */
public class StrictHttpFirewall implements HttpFirewall {
	/**
	 * Used to specify to {@link #setAllowedHttpMethods(Collection)} that any HTTP method should be allowed.
	 */
	private static final Set<String> ALLOW_ANY_HTTP_METHOD = Collections.unmodifiableSet(Collections.emptySet());

	private static final String ENCODED_PERCENT = "%25";

	private static final String PERCENT = "%";

	private static final List<String> FORBIDDEN_ENCODED_PERIOD = Collections.unmodifiableList(Arrays.asList("%2e", "%2E"));

	private static final List<String> FORBIDDEN_SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));

	private static final List<String> FORBIDDEN_FORWARDSLASH = Collections.unmodifiableList(Arrays.asList("%2f", "%2F"));

	private static final List<String> FORBIDDEN_DOUBLE_FORWARDSLASH = Collections.unmodifiableList(Arrays.asList("//", "%2f%2f", "%2f%2F", "%2F%2f", "%2F%2F"));

	private static final List<String> FORBIDDEN_BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));

	private Set<String> encodedUrlBlacklist = new HashSet<String>();

	private Set<String> decodedUrlBlacklist = new HashSet<String>();

	private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods();

	public StrictHttpFirewall() {
		urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
		urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
		urlBlacklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
		urlBlacklistsAddAll(FORBIDDEN_BACKSLASH);

		this.encodedUrlBlacklist.add(ENCODED_PERCENT);
		this.encodedUrlBlacklist.addAll(FORBIDDEN_ENCODED_PERIOD);
		this.decodedUrlBlacklist.add(PERCENT);
	}

	/**
	 * Sets if any HTTP method is allowed. If this set to true, then no validation on the HTTP method will be performed.
	 * This can open the application up to
	 * HTTP Verb tampering and XST attacks
	 * @param unsafeAllowAnyHttpMethod if true, disables HTTP method validation, else resets back to the defaults. Default is false.
	 * @see #setAllowedHttpMethods(Collection)
	 * @since 5.1
	 */
	public void setUnsafeAllowAnyHttpMethod(boolean unsafeAllowAnyHttpMethod) {
		this.allowedHttpMethods = unsafeAllowAnyHttpMethod ? ALLOW_ANY_HTTP_METHOD : createDefaultAllowedHttpMethods();
	}

	/**
	 *

	 * Determines which HTTP methods should be allowed. The default is to allow "DELETE", "GET", "HEAD", "OPTIONS",
	 * "PATCH", "POST", and "PUT".
	 *

	 *
	 * @param allowedHttpMethods the case-sensitive collection of HTTP methods that are allowed.
	 * @see #setUnsafeAllowAnyHttpMethod(boolean)
	 * @since 5.1
	 */
	public void setAllowedHttpMethods(Collection<String> allowedHttpMethods) {
		if (allowedHttpMethods == null) {
			throw new IllegalArgumentException("allowedHttpMethods cannot be null");
		}
		if (allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
			this.allowedHttpMethods = ALLOW_ANY_HTTP_METHOD;
		} else {
			this.allowedHttpMethods = new HashSet<>(allowedHttpMethods);
		}
	}

	/**
	 *

	 * Determines if semicolon is allowed in the URL (i.e. matrix variables). The default
	 * is to disable this behavior because it is a common way of attempting to perform
	 * Reflected File Download Attacks.
	 * It is also the source of many exploits which bypass URL based security.
	 *

	 *
For example, the following CVEs are a subset of the issues related
	 * to ambiguities in the Servlet Specification on how to treat semicolons that
	 * led to CVEs:
	 *

	 *

	 *
cve-2016-5007

	 *
cve-2016-9879

	 *
cve-2018-1199

	 *

	 *
	 *

	 * If you are wanting to allow semicolons, please reconsider as it is a very common
	 * source of security bypasses. A few common reasons users want semicolons and
	 * alternatives are listed below:
	 *

	 *

	 *
Including the JSESSIONID in the path - You should not include session id (or
	 * any sensitive information) in a URL as it can lead to leaking. Instead use Cookies.
	 *

	 *
Matrix Variables - Users wanting to leverage Matrix Variables should consider
	 * using HTTP parameters instead.
	 *

	 *

	 *
	 * @param allowSemicolon should semicolons be allowed in the URL. Default is false
	 */
	public void setAllowSemicolon(boolean allowSemicolon) {
		if (allowSemicolon) {
			urlBlacklistsRemoveAll(FORBIDDEN_SEMICOLON);
		} else {
			urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
		}
	}

	/**
	 *

	 * Determines if a slash "/" that is URL encoded "%2F" should be allowed in the path
	 * or not. The default is to not allow this behavior because it is a common way to
	 * bypass URL based security.
	 *

	 *

	 * For example, due to ambiguities in the servlet specification, the value is not
	 * parsed consistently which results in different values in {@code HttpServletRequest}
	 * path related values which allow bypassing certain security constraints.
	 *

	 *
	 * @param allowUrlEncodedSlash should a slash "/" that is URL encoded "%2F" be allowed
	 * in the path or not. Default is false.
	 */
	public void setAllowUrlEncodedSlash(boolean allowUrlEncodedSlash) {
		if (allowUrlEncodedSlash) {
			urlBlacklistsRemoveAll(FORBIDDEN_FORWARDSLASH);
		} else {
			urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
		}
	}

	/**
	 *

	 * Determines if double slash "//" that is URL encoded "%2F%2F" should be allowed in the path or
	 * not. The default is to not allow.
	 *

	 *
	 * @param allowUrlEncodedDoubleSlash should a slash "//" that is URL encoded "%2F%2F" be allowed
	 *        in the path or not. Default is false.
	 */
	public void setAllowUrlEncodedDoubleSlash(boolean allowUrlEncodedDoubleSlash) {
		if (allowUrlEncodedDoubleSlash) {
			urlBlacklistsRemoveAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
		} else {
			urlBlacklistsAddAll(FORBIDDEN_DOUBLE_FORWARDSLASH);
		}
	}

	/**
	 *

	 * Determines if a period "." that is URL encoded "%2E" should be allowed in the path
	 * or not. The default is to not allow this behavior because it is a frequent source
	 * of security exploits.
	 *

	 *

	 * For example, due to ambiguities in the servlet specification a URL encoded period
	 * might lead to bypassing security constraints through a directory traversal attack.
	 * This is because the path is not parsed consistently which results  in different
	 * values in {@code HttpServletRequest} path related values which allow bypassing
	 * certain security constraints.
	 *

	 *
	 * @param allowUrlEncodedPeriod should a period "." that is URL encoded "%2E" be
	 * allowed in the path or not. Default is false.
	 */
	public void setAllowUrlEncodedPeriod(boolean allowUrlEncodedPeriod) {
		if (allowUrlEncodedPeriod) {
			this.encodedUrlBlacklist.removeAll(FORBIDDEN_ENCODED_PERIOD);
		} else {
			this.encodedUrlBlacklist.addAll(FORBIDDEN_ENCODED_PERIOD);
		}
	}

	/**
	 *

	 * Determines if a backslash "\" or a URL encoded backslash "%5C" should be allowed in
	 * the path or not. The default is not to allow this behavior because it is a frequent
	 * source of security exploits.
	 *

	 *

	 * For example, due to ambiguities in the servlet specification a URL encoded period
	 * might lead to bypassing security constraints through a directory traversal attack.
	 * This is because the path is not parsed consistently which results  in different
	 * values in {@code HttpServletRequest} path related values which allow bypassing
	 * certain security constraints.
	 *

	 *
	 * @param allowBackSlash a backslash "\" or a URL encoded backslash "%5C" be allowed
	 * in the path or not. Default is false
	 */
	public void setAllowBackSlash(boolean allowBackSlash) {
		if (allowBackSlash) {
			urlBlacklistsRemoveAll(FORBIDDEN_BACKSLASH);
		} else {
			urlBlacklistsAddAll(FORBIDDEN_BACKSLASH);
		}
	}

	/**
	 *

	 * Determines if a percent "%" that is URL encoded "%25" should be allowed in the path
	 * or not. The default is not to allow this behavior because it is a frequent source
	 * of security exploits.
	 *

	 *

	 * For example, this can lead to exploits that involve double URL encoding that lead
	 * to bypassing security constraints.
	 *

	 *
	 * @param allowUrlEncodedPercent if a percent "%" that is URL encoded "%25" should be
	 * allowed in the path or not. Default is false
	 */
	public void setAllowUrlEncodedPercent(boolean allowUrlEncodedPercent) {
		if (allowUrlEncodedPercent) {
			this.encodedUrlBlacklist.remove(ENCODED_PERCENT);
			this.decodedUrlBlacklist.remove(PERCENT);
		} else {
			this.encodedUrlBlacklist.add(ENCODED_PERCENT);
			this.decodedUrlBlacklist.add(PERCENT);
		}
	}

	private void urlBlacklistsAddAll(Collection<String> values) {
		this.encodedUrlBlacklist.addAll(values);
		this.decodedUrlBlacklist.addAll(values);
	}

	private void urlBlacklistsRemoveAll(Collection<String> values) {
		this.encodedUrlBlacklist.removeAll(values);
		this.decodedUrlBlacklist.removeAll(values);
	}

	@Override
	public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
		rejectForbiddenHttpMethod(request);
		rejectedBlacklistedUrls(request);

		if (!isNormalized(request)) {
			throw new RequestRejectedException("The request was rejected because the URL was not normalized.");
		}

		String requestUri = request.getRequestURI();
		if (!containsOnlyPrintableAsciiCharacters(requestUri)) {
			throw new RequestRejectedException("The requestURI was rejected because it can only contain printable ASCII characters.");
		}
		return new FirewalledRequest(request) {
			@Override
			public void reset() {
			}
		};
	}

	private void rejectForbiddenHttpMethod(HttpServletRequest request) {
		if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
			return;
		}
		if (!this.allowedHttpMethods.contains(request.getMethod())) {
			throw new RequestRejectedException("The request was rejected because the HTTP method \"" +
					request.getMethod() +
					"\" was not included within the whitelist " +
					this.allowedHttpMethods);
		}
	}

	private void rejectedBlacklistedUrls(HttpServletRequest request) {
		for (String forbidden : this.encodedUrlBlacklist) {
			if (encodedUrlContains(request, forbidden)) {
				throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"");
			}
		}
		for (String forbidden : this.decodedUrlBlacklist) {
			if (decodedUrlContains(request, forbidden)) {
				throw new RequestRejectedException("The request was rejected because the URL contained a potentially malicious String \"" + forbidden + "\"");
			}
		}
	}

	@Override
	public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
		return new FirewalledResponse(response);
	}

	private static Set<String> createDefaultAllowedHttpMethods() {
		Set<String> result = new HashSet<>();
		result.add(HttpMethod.DELETE.name());
		result.add(HttpMethod.GET.name());
		result.add(HttpMethod.HEAD.name());
		result.add(HttpMethod.OPTIONS.name());
		result.add(HttpMethod.PATCH.name());
		result.add(HttpMethod.POST.name());
		result.add(HttpMethod.PUT.name());
		return result;
	}

	private static boolean isNormalized(HttpServletRequest request) {
		if (!isNormalized(request.getRequestURI())) {
			return false;
		}
		if (!isNormalized(request.getContextPath())) {
			return false;
		}
		if (!isNormalized(request.getServletPath())) {
			return false;
		}
		if (!isNormalized(request.getPathInfo())) {
			return false;
		}
		return true;
	}

	private static boolean encodedUrlContains(HttpServletRequest request, String value) {
		if (valueContains(request.getContextPath(), value)) {
			return true;
		}
		return valueContains(request.getRequestURI(), value);
	}

	private static boolean decodedUrlContains(HttpServletRequest request, String value) {
		if (valueContains(request.getServletPath(), value)) {
			return true;
		}
		if (valueContains(request.getPathInfo(), value)) {
			return true;
		}
		return false;
	}

	private static boolean containsOnlyPrintableAsciiCharacters(String uri) {
		int length = uri.length();
		for (int i = 0; i < length; i++) {
			char c = uri.charAt(i);
			if (c < '\u0020' || c > '\u007e') {
				return false;
			}
		}

		return true;
	}

	private static boolean valueContains(String value, String contains) {
		return value != null && value.contains(contains);
	}

	/**
	 * Checks whether a path is normalized (doesn't contain path traversal
	 * sequences like "./", "/../" or "/.")
	 *
	 * @param path
	 *            the path to test
	 * @return true if the path doesn't contain any path-traversal character
	 *         sequences.
	 */
	private static boolean isNormalized(String path) {
		if (path == null) {
			return true;
		}

		for (int j = path.length(); j > 0;) {
			int i = path.lastIndexOf('/', j - 1);
			int gap = j - i;

			if (gap == 2 && path.charAt(i + 1) == '.') {
				// ".", "/./" or "/."
				return false;
			} else if (gap == 3 && path.charAt(i + 1) == '.' && path.charAt(i + 2) == '.') {
				return false;
			}

			j = i;
		}

		return true;
	}

	/**
	 * Provides the existing encoded url blacklist which can add/remove entries from
	 *
	 * @return the existing encoded url blacklist, never null
	 */
	public Set<String> getEncodedUrlBlacklist() {
		return encodedUrlBlacklist;
	}

	/**
	 * Provides the existing decoded url blacklist which can add/remove entries from
	 *
	 * @return the existing decoded url blacklist, never null
	 */
	public Set<String> getDecodedUrlBlacklist() {
		return decodedUrlBlacklist;
	}
}


/*
 * Copyright 2002-2016 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.core.authority.mapping;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.util.Assert;

import java.util.*;

/**
 * Simple one-to-one {@code GrantedAuthoritiesMapper} which allows for case conversion of
 * the authority name and the addition of a string prefix (which defaults to {@code ROLE_}
 * ).
 *
 * @author Luke Taylor
 * @since 3.1
 */
public final class SimpleAuthorityMapper implements GrantedAuthoritiesMapper,
		InitializingBean {
	private GrantedAuthority defaultAuthority;
	private String prefix = "ROLE_";
	private boolean convertToUpperCase = false;
	private boolean convertToLowerCase = false;

	public void afterPropertiesSet() throws Exception {
		Assert.isTrue(!(convertToUpperCase && convertToLowerCase),
				"Either convertToUpperCase or convertToLowerCase can be set to true, but not both");
	}

	/**
	 * Creates a mapping of the supplied authorities based on the case-conversion and
	 * prefix settings. The mapping will be one-to-one unless duplicates are produced
	 * during the conversion. If a default authority has been set, this will also be
	 * assigned to each mapping.
	 *
	 * @param authorities the original authorities
	 *
	 * @return the converted set of authorities
	 */
	public Set<GrantedAuthority> mapAuthorities(
			Collection<? extends GrantedAuthority> authorities) {
		HashSet<GrantedAuthority> mapped = new HashSet<>(
				authorities.size());
		for (GrantedAuthority authority : authorities) {
			mapped.add(mapAuthority(authority.getAuthority()));
		}

		if (defaultAuthority != null) {
			mapped.add(defaultAuthority);
		}

		return mapped;
	}

	private GrantedAuthority mapAuthority(String name) {
		if (convertToUpperCase) {
			name = name.toUpperCase();
		}
		else if (convertToLowerCase) {
			name = name.toLowerCase();
		}

		if (prefix.length() > 0 && !name.startsWith(prefix)) {
			name = prefix + name;
		}

		return new SimpleGrantedAuthority(name);
	}

	/**
	 * Sets the prefix which should be added to the authority name (if it doesn't already
	 * exist)
	 *
	 * @param prefix the prefix, typically to satisfy the behaviour of an
	 * {@code AccessDecisionVoter}.
	 */
	public void setPrefix(String prefix) {
		Assert.notNull(prefix, "prefix cannot be null");
		this.prefix = prefix;
	}

	/**
	 * Whether to convert the authority value to upper case in the mapping.
	 *
	 * @param convertToUpperCase defaults to {@code false}
	 */
	public void setConvertToUpperCase(boolean convertToUpperCase) {
		this.convertToUpperCase = convertToUpperCase;
	}

	/**
	 * Whether to convert the authority value to lower case in the mapping.
	 *
	 * @param convertToLowerCase defaults to {@code false}
	 */
	public void setConvertToLowerCase(boolean convertToLowerCase) {
		this.convertToLowerCase = convertToLowerCase;
	}

	/**
	 * Sets a default authority to be assigned to all users
	 *
	 * @param authority the name of the authority to be assigned to all users.
	 */
	public void setDefaultAuthority(String authority) {
		Assert.hasText(authority, "The authority name cannot be set to an empty value");
		this.defaultAuthority = new SimpleGrantedAuthority(authority);
	}
}


/*
 * Copyright 2002-2018 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.config.annotation.web.configurers;

import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.openid.OpenIDLoginConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.PortMapper;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;

/**
 * Base class for configuring {@link AbstractAuthenticationFilterConfigurer}. This is
 * intended for internal use only.
 *
 * @see FormLoginConfigurer
 * @see OpenIDLoginConfigurer
 *
 * @param T refers to "this" for returning the current configurer
 * @param F refers to the {@link AbstractAuthenticationProcessingFilter} that is being
 * built
 *
 * @author Rob Winch
 * @since 3.2
 */
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder, T extends AbstractAuthenticationFilterConfigurer, F extends AbstractAuthenticationProcessingFilter>
		extends AbstractHttpConfigurer<T, B> {

	private F authFilter;

	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource;

	private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
	private AuthenticationSuccessHandler successHandler = this.defaultSuccessHandler;

	private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;

	private boolean customLoginPage;
	private String loginPage;
	private String loginProcessingUrl;

	private AuthenticationFailureHandler failureHandler;

	private boolean permitAll;

	private String failureUrl;

	/**
	 * Creates a new instance with minimal defaults
	 */
	protected AbstractAuthenticationFilterConfigurer() {
		setLoginPage("/login");
	}

	/**
	 * Creates a new instance
	 * @param authenticationFilter the {@link AbstractAuthenticationProcessingFilter} to
	 * use
	 * @param defaultLoginProcessingUrl the default URL to use for
	 * {@link #loginProcessingUrl(String)}
	 */
	protected AbstractAuthenticationFilterConfigurer(F authenticationFilter,
			String defaultLoginProcessingUrl) {
		this();
		this.authFilter = authenticationFilter;
		if (defaultLoginProcessingUrl != null) {
			loginProcessingUrl(defaultLoginProcessingUrl);
		}
	}

	/**
	 * Specifies where users will go after authenticating successfully if they have not
	 * visited a secured page prior to authenticating. This is a shortcut for calling
	 * {@link #defaultSuccessUrl(String)}.
	 *
	 * @param defaultSuccessUrl the default success url
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T defaultSuccessUrl(String defaultSuccessUrl) {
		return defaultSuccessUrl(defaultSuccessUrl, false);
	}

	/**
	 * Specifies where users will go after authenticating successfully if they have not
	 * visited a secured page prior to authenticating or {@code alwaysUse} is true. This
	 * is a shortcut for calling {@link #successHandler(AuthenticationSuccessHandler)}.
	 *
	 * @param defaultSuccessUrl the default success url
	 * @param alwaysUse true if the {@code defaultSuccesUrl} should be used after
	 * authentication despite if a protected page had been previously visited
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse) {
		SavedRequestAwareAuthenticationSuccessHandler handler = new SavedRequestAwareAuthenticationSuccessHandler();
		handler.setDefaultTargetUrl(defaultSuccessUrl);
		handler.setAlwaysUseDefaultTargetUrl(alwaysUse);
		this.defaultSuccessHandler = handler;
		return successHandler(handler);
	}

	/**
	 * Specifies the URL to validate the credentials.
	 *
	 * @param loginProcessingUrl the URL to validate username and password
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public T loginProcessingUrl(String loginProcessingUrl) {
		this.loginProcessingUrl = loginProcessingUrl;
		authFilter
				.setRequiresAuthenticationRequestMatcher(createLoginProcessingUrlMatcher(loginProcessingUrl));
		return getSelf();
	}

	/**
	 * Create the {@link RequestMatcher} given a loginProcessingUrl
	 * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the
	 * loginProcessingUrl
	 * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl
	 */
	protected abstract RequestMatcher createLoginProcessingUrlMatcher(
			String loginProcessingUrl);

	/**
	 * Specifies a custom {@link AuthenticationDetailsSource}. The default is
	 * {@link WebAuthenticationDetailsSource}.
	 *
	 * @param authenticationDetailsSource the custom {@link AuthenticationDetailsSource}
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T authenticationDetailsSource(
			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
		this.authenticationDetailsSource = authenticationDetailsSource;
		return getSelf();
	}

	/**
	 * Specifies the {@link AuthenticationSuccessHandler} to be used. The default is
	 * {@link SavedRequestAwareAuthenticationSuccessHandler} with no additional properites
	 * set.
	 *
	 * @param successHandler the {@link AuthenticationSuccessHandler}.
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T successHandler(AuthenticationSuccessHandler successHandler) {
		this.successHandler = successHandler;
		return getSelf();
	}

	/**
	 * Equivalent of invoking permitAll(true)
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T permitAll() {
		return permitAll(true);
	}

	/**
	 * Ensures the urls for {@link #failureUrl(String)} as well as for the {@link HttpSecurityBuilder}, the
	 * {@link #getLoginPage} and {@link #getLoginProcessingUrl} are granted access to any user.
	 *
	 * @param permitAll true to grant access to the URLs false to skip this step
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T permitAll(boolean permitAll) {
		this.permitAll = permitAll;
		return getSelf();
	}

	/**
	 * The URL to send users if authentication fails. This is a shortcut for invoking
	 * {@link #failureHandler(AuthenticationFailureHandler)}. The default is
	 * "/login?error".
	 *
	 * @param authenticationFailureUrl the URL to send users if authentication fails (i.e.
	 * "/login?error").
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T failureUrl(String authenticationFailureUrl) {
		T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(
				authenticationFailureUrl));
		this.failureUrl = authenticationFailureUrl;
		return result;
	}

	/**
	 * Specifies the {@link AuthenticationFailureHandler} to use when authentication
	 * fails. The default is redirecting to "/login?error" using
	 * {@link SimpleUrlAuthenticationFailureHandler}
	 *
	 * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} to use
	 * when authentication fails.
	 * @return the {@link FormLoginConfigurer} for additional customization
	 */
	public final T failureHandler(
			AuthenticationFailureHandler authenticationFailureHandler) {
		this.failureUrl = null;
		this.failureHandler = authenticationFailureHandler;
		return getSelf();
	}

	@Override
	public void init(B http) throws Exception {
		updateAuthenticationDefaults();
		updateAccessDefaults(http);
		registerDefaultAuthenticationEntryPoint(http);
	}

	@SuppressWarnings("unchecked")
	protected final void registerDefaultAuthenticationEntryPoint(B http) {
		registerAuthenticationEntryPoint(http, this.authenticationEntryPoint);
	}

	@SuppressWarnings("unchecked")
	protected final void registerAuthenticationEntryPoint(B http, AuthenticationEntryPoint authenticationEntryPoint) {
		ExceptionHandlingConfigurer exceptionHandling = http
				.getConfigurer(ExceptionHandlingConfigurer.class);
		if (exceptionHandling == null) {
			return;
		}
		exceptionHandling.defaultAuthenticationEntryPointFor(
				postProcess(authenticationEntryPoint), getAuthenticationEntryPointMatcher(http));
	}

	protected final RequestMatcher getAuthenticationEntryPointMatcher(B http) {
		ContentNegotiationStrategy contentNegotiationStrategy = http
				.getSharedObject(ContentNegotiationStrategy.class);
		if (contentNegotiationStrategy == null) {
			contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
		}

		MediaTypeRequestMatcher mediaMatcher = new MediaTypeRequestMatcher(
				contentNegotiationStrategy, MediaType.APPLICATION_XHTML_XML,
				new MediaType("image", "*"), MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
		mediaMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));

		RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
				new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));

		return new AndRequestMatcher(Arrays.asList(notXRequestedWith, mediaMatcher));
	}

	@Override
	public void configure(B http) throws Exception {
		PortMapper portMapper = http.getSharedObject(PortMapper.class);
		if (portMapper != null) {
			authenticationEntryPoint.setPortMapper(portMapper);
		}

		RequestCache requestCache = http.getSharedObject(RequestCache.class);
		if (requestCache != null) {
			this.defaultSuccessHandler.setRequestCache(requestCache);
		}

		authFilter.setAuthenticationManager(http
				.getSharedObject(AuthenticationManager.class));
		authFilter.setAuthenticationSuccessHandler(successHandler);
		authFilter.setAuthenticationFailureHandler(failureHandler);
		if (authenticationDetailsSource != null) {
			authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
		}
		SessionAuthenticationStrategy sessionAuthenticationStrategy = http
				.getSharedObject(SessionAuthenticationStrategy.class);
		if (sessionAuthenticationStrategy != null) {
			authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
		}
		RememberMeServices rememberMeServices = http
				.getSharedObject(RememberMeServices.class);
		if (rememberMeServices != null) {
			authFilter.setRememberMeServices(rememberMeServices);
		}
		F filter = postProcess(authFilter);
		http.addFilter(filter);
	}

	/**
	 *

	 * Specifies the URL to send users to if login is required. If used with
	 * {@link WebSecurityConfigurerAdapter} a default login page will be generated when
	 * this attribute is not specified.
	 *

	 *
	 *

	 * If a URL is specified or this is not being used in conjuction with
	 * {@link WebSecurityConfigurerAdapter}, users are required to process the specified
	 * URL to generate a login page.
	 *

	 */
	protected T loginPage(String loginPage) {
		setLoginPage(loginPage);
		updateAuthenticationDefaults();
		this.customLoginPage = true;
		return getSelf();
	}

	/**
	 *
	 * @return true if a custom login page has been specified, else false
	 */
	public final boolean isCustomLoginPage() {
		return customLoginPage;
	}

	/**
	 * Gets the Authentication Filter
	 *
	 * @return the Authentication Filter
	 */
	protected final F getAuthenticationFilter() {
		return authFilter;
	}

	/**
	 * Sets the Authentication Filter
	 *
	 * @param authFilter the Authentication Filter
	 */
	protected final void setAuthenticationFilter(F authFilter) {
		this.authFilter = authFilter;
	}

	/**
	 * Gets the login page
	 *
	 * @return the login page
	 */
	protected final String getLoginPage() {
		return loginPage;
	}

	/**
	 * Gets the Authentication Entry Point
	 *
	 * @return the Authentication Entry Point
	 */
	protected final AuthenticationEntryPoint getAuthenticationEntryPoint() {
		return authenticationEntryPoint;
	}

	/**
	 * Gets the URL to submit an authentication request to (i.e. where username/password
	 * must be submitted)
	 *
	 * @return the URL to submit an authentication request to
	 */
	protected final String getLoginProcessingUrl() {
		return loginProcessingUrl;
	}

	/**
	 * Gets the URL to send users to if authentication fails
	 *
	 * @return the URL to send users if authentication fails (e.g. "/login?error").
	 */
	protected final String getFailureUrl() {
		return failureUrl;
	}

	/**
	 * Updates the default values for authentication.
	 *
	 * @throws Exception
	 */
	protected final void updateAuthenticationDefaults() {
		if (loginProcessingUrl == null) {
			loginProcessingUrl(loginPage);
		}
		if (failureHandler == null) {
			failureUrl(loginPage + "?error");
		}

		final LogoutConfigurer logoutConfigurer = getBuilder().getConfigurer(
				LogoutConfigurer.class);
		if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
			logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
		}
	}

	/**
	 * Updates the default values for access.
	 */
	protected final void updateAccessDefaults(B http) {
		if (permitAll) {
			PermitAllSupport.permitAll(http, loginPage, loginProcessingUrl, failureUrl);
		}
	}

	/**
	 * Sets the loginPage and updates the {@link AuthenticationEntryPoint}.
	 * @param loginPage
	 */
	private void setLoginPage(String loginPage) {
		this.loginPage = loginPage;
		this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);
	}

	@SuppressWarnings("unchecked")
	private T getSelf() {
		return (T) this;
	}
}