/*
 * Copyright 2002-2013 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
 *
 *      http://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 java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.authority.mapping.SimpleMappableAttributesRetriever;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.Http403ForbiddenEntryPoint;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService;
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;

/**
 * Adds support for J2EE pre authentication.
 *
 * <h2>Security Filters</h2>
 *
 * The following Filters are populated
 *
 * <ul>
 * <li>
 * {@link J2eePreAuthenticatedProcessingFilter}</li>
 * </ul>
 *
 * <h2>Shared Objects Created</h2>
 *
 * <ul>
 * <li>
 * {@link AuthenticationEntryPoint} is populated with an
 * {@link Http403ForbiddenEntryPoint}</li>
 * <li>A {@link PreAuthenticatedAuthenticationProvider} is populated into
 * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
 * </li>
 * </ul>
 *
 * <h2>Shared Objects Used</h2>
 *
 * The following shared objects are used:
 *
 * <ul>
 * <li>{@link AuthenticationManager}</li>
 * </ul>
 *
 * @author Rob Winch
 * @since 3.2
 */
public final class JeeConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractHttpConfigurer<JeeConfigurer<H>, H> {
	private J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter;
	private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticationUserDetailsService;
	private Set<String> mappableRoles = new HashSet<String>();

	/**
	 * Creates a new instance
	 * @see HttpSecurity#jee()
	 */
	public JeeConfigurer() {
	}

	/**
	 * Specifies roles to use map from the {@link HttpServletRequest} to the
	 * {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)} returns
	 * true, the role is added to the {@link UserDetails}. This method is the equivalent
	 * of invoking {@link #mappableAuthorities(Set)}. Multiple invocations of
	 * {@link #mappableAuthorities(String...)} will override previous invocations.
	 *
	 * <p>
	 * There are no default roles that are mapped.
	 * </p>
	 *
	 * @param mappableRoles the roles to attempt to map to the {@link UserDetails} (i.e.
	 * "ROLE_USER", "ROLE_ADMIN", etc).
	 * @return the {@link JeeConfigurer} for further customizations
	 * @see SimpleMappableAttributesRetriever
	 * @see #mappableRoles(String...)
	 */
	public JeeConfigurer<H> mappableAuthorities(String... mappableRoles) {
		this.mappableRoles.clear();
		for (String role : mappableRoles) {
			this.mappableRoles.add(role);
		}
		return this;
	}

	/**
	 * Specifies roles to use map from the {@link HttpServletRequest} to the
	 * {@link UserDetails} and automatically prefixes it with "ROLE_". If
	 * {@link HttpServletRequest#isUserInRole(String)} returns true, the role is added to
	 * the {@link UserDetails}. This method is the equivalent of invoking
	 * {@link #mappableAuthorities(Set)}. Multiple invocations of
	 * {@link #mappableRoles(String...)} will override previous invocations.
	 *
	 * <p>
	 * There are no default roles that are mapped.
	 * </p>
	 *
	 * @param mappableRoles the roles to attempt to map to the {@link UserDetails} (i.e.
	 * "USER", "ADMIN", etc).
	 * @return the {@link JeeConfigurer} for further customizations
	 * @see SimpleMappableAttributesRetriever
	 * @see #mappableAuthorities(String...)
	 */
	public JeeConfigurer<H> mappableRoles(String... mappableRoles) {
		this.mappableRoles.clear();
		for (String role : mappableRoles) {
			this.mappableRoles.add("ROLE_" + role);
		}
		return this;
	}

	/**
	 * Specifies roles to use map from the {@link HttpServletRequest} to the
	 * {@link UserDetails}. If {@link HttpServletRequest#isUserInRole(String)} returns
	 * true, the role is added to the {@link UserDetails}. This is the equivalent of
	 * {@link #mappableRoles(String...)}. Multiple invocations of
	 * {@link #mappableAuthorities(Set)} will override previous invocations.
	 *
	 * <p>
	 * There are no default roles that are mapped.
	 * </p>
	 *
	 * @param mappableRoles the roles to attempt to map to the {@link UserDetails}.
	 * @return the {@link JeeConfigurer} for further customizations
	 * @see SimpleMappableAttributesRetriever
	 */
	public JeeConfigurer<H> mappableAuthorities(Set<String> mappableRoles) {
		this.mappableRoles = mappableRoles;
		return this;
	}

	/**
	 * Specifies the {@link AuthenticationUserDetailsService} that is used with the
	 * {@link PreAuthenticatedAuthenticationProvider}. The default is a
	 * {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
	 *
	 * @param authenticatedUserDetailsService the {@link AuthenticationUserDetailsService}
	 * to use.
	 * @return the {@link JeeConfigurer} for further configuration
	 */
	public JeeConfigurer<H> authenticatedUserDetailsService(
			AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> authenticatedUserDetailsService) {
		this.authenticationUserDetailsService = authenticatedUserDetailsService;
		return this;
	}

	/**
	 * Allows specifying the {@link J2eePreAuthenticatedProcessingFilter} to use. If
	 * {@link J2eePreAuthenticatedProcessingFilter} is provided, all of its attributes
	 * must also be configured manually (i.e. all attributes populated in the
	 * {@link JeeConfigurer} are not used).
	 *
	 * @param j2eePreAuthenticatedProcessingFilter the
	 * {@link J2eePreAuthenticatedProcessingFilter} to use.
	 * @return the {@link JeeConfigurer} for further configuration
	 */
	public JeeConfigurer<H> j2eePreAuthenticatedProcessingFilter(
			J2eePreAuthenticatedProcessingFilter j2eePreAuthenticatedProcessingFilter) {
		this.j2eePreAuthenticatedProcessingFilter = j2eePreAuthenticatedProcessingFilter;
		return this;
	}

	/**
	 * Populates a {@link PreAuthenticatedAuthenticationProvider} into
	 * {@link HttpSecurity#authenticationProvider(org.springframework.security.authentication.AuthenticationProvider)}
	 * and a {@link Http403ForbiddenEntryPoint} into
	 * {@link HttpSecurityBuilder#setSharedObject(Class, Object)}
	 *
	 * @see org.springframework.security.config.annotation.SecurityConfigurerAdapter#init(org.springframework.security.config.annotation.SecurityBuilder)
	 */
	@Override
	public void init(H http) throws Exception {
		PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
		authenticationProvider
				.setPreAuthenticatedUserDetailsService(getUserDetailsService());
		authenticationProvider = postProcess(authenticationProvider);

		// @formatter:off
		http
			.authenticationProvider(authenticationProvider)
			.setSharedObject(AuthenticationEntryPoint.class,new Http403ForbiddenEntryPoint());
		// @formatter:on
	}

	@Override
	public void configure(H http) throws Exception {
		J2eePreAuthenticatedProcessingFilter filter = getFilter(http
				.getSharedObject(AuthenticationManager.class));
		http.addFilter(filter);
	}

	/**
	 * Gets the {@link J2eePreAuthenticatedProcessingFilter} or creates a default instance
	 * using the properties provided.
	 * @param authenticationManager the {@link AuthenticationManager} to use.
	 * @return the {@link J2eePreAuthenticatedProcessingFilter} to use.
	 */
	private J2eePreAuthenticatedProcessingFilter getFilter(
			AuthenticationManager authenticationManager) {
		if (j2eePreAuthenticatedProcessingFilter == null) {
			j2eePreAuthenticatedProcessingFilter = new J2eePreAuthenticatedProcessingFilter();
			j2eePreAuthenticatedProcessingFilter
					.setAuthenticationManager(authenticationManager);
			j2eePreAuthenticatedProcessingFilter
					.setAuthenticationDetailsSource(createWebAuthenticationDetailsSource());
			j2eePreAuthenticatedProcessingFilter = postProcess(j2eePreAuthenticatedProcessingFilter);
		}

		return j2eePreAuthenticatedProcessingFilter;
	}

	/**
	 * Gets the {@link AuthenticationUserDetailsService} that was specified or defaults to
	 * {@link PreAuthenticatedGrantedAuthoritiesUserDetailsService}.
	 *
	 * @return the {@link AuthenticationUserDetailsService} to use
	 */
	private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> getUserDetailsService() {
		return authenticationUserDetailsService == null ? new PreAuthenticatedGrantedAuthoritiesUserDetailsService()
				: authenticationUserDetailsService;
	}

	/**
	 * Creates the {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to set
	 * on the {@link J2eePreAuthenticatedProcessingFilter}. It is populated with a
	 * {@link SimpleMappableAttributesRetriever}.
	 *
	 * @return the {@link J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource} to use.
	 */
	private J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource createWebAuthenticationDetailsSource() {
		J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource detailsSource = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource();
		SimpleMappableAttributesRetriever rolesRetriever = new SimpleMappableAttributesRetriever();
		rolesRetriever.setMappableAttributes(mappableRoles);
		detailsSource.setMappableRolesRetriever(rolesRetriever);

		detailsSource = postProcess(detailsSource);
		return detailsSource;
	}
}