package org.springframework.security.config.http;

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.security.config.ConfigTestUtils.AUTH_PROVIDER_XML;
import static org.springframework.security.config.http.AuthenticationConfigBuilder.*;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;

import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.RememberMeAuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.PostProcessedMockUserDetailsService;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.openid.OpenID4JavaConsumer;
import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationFilter;
import org.springframework.security.openid.OpenIDAuthenticationProvider;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.openid.OpenIDConsumer;
import org.springframework.security.openid.OpenIDConsumerException;
import org.springframework.security.util.FieldUtils;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.PortMapperImpl;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
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.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper;
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.ConcurrentSessionFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ReflectionUtils;

/**
 * @author Luke Taylor
 */
public class HttpSecurityBeanDefinitionParserTests {
    private static final int AUTO_CONFIG_FILTERS = 11;
    private AbstractXmlApplicationContext appContext;

    @After
    public void closeAppContext() {
        if (appContext != null) {
            appContext.close();
            appContext = null;
        }
        SecurityContextHolder.clearContext();
    }

    @Test
    public void minimalConfigurationParses() {
        setContext("<http><http-basic /></http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void beanClassNamesAreCorrect() throws Exception {
        assertEquals(DefaultWebSecurityExpressionHandler.class.getName(), HttpSecurityBeanDefinitionParser.EXPRESSION_HANDLER_CLASS);
        assertEquals(ExpressionBasedFilterInvocationSecurityMetadataSource.class.getName(), HttpSecurityBeanDefinitionParser.EXPRESSION_FIMDS_CLASS);
        assertEquals(UsernamePasswordAuthenticationFilter.class.getName(), AUTHENTICATION_PROCESSING_FILTER_CLASS);
        assertEquals(OpenIDAuthenticationFilter.class.getName(), OPEN_ID_AUTHENTICATION_PROCESSING_FILTER_CLASS);
        assertEquals(OpenIDAuthenticationProvider.class.getName(), OPEN_ID_AUTHENTICATION_PROVIDER_CLASS);
    }

    @Test
    public void httpAutoConfigSetsUpCorrectFilterList() throws Exception {
        setContext("<http auto-config='true' />" + AUTH_PROVIDER_XML);

        List<Filter> filterList = getFilters("/anyurl");

        checkAutoConfigFilters(filterList);

        assertEquals(true, FieldUtils.getFieldValue(appContext.getBean(BeanIds.FILTER_CHAIN_PROXY), "stripQueryStringFromUrls"));
        assertEquals(true, FieldUtils.getFieldValue(filterList.get(AUTO_CONFIG_FILTERS-1), "securityMetadataSource.stripQueryStringFromUrls"));
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void duplicateElementCausesError() throws Exception {
        setContext("<http auto-config='true' /><http auto-config='true' />" + AUTH_PROVIDER_XML);
    }

    private void checkAutoConfigFilters(List<Filter> filterList) throws Exception {
//        assertEquals("Expected " + AUTO_CONFIG_FILTERS + " filters in chain", AUTO_CONFIG_FILTERS, filterList.size());

        Iterator<Filter> filters = filterList.iterator();

        assertTrue(filters.next() instanceof SecurityContextPersistenceFilter);
        assertTrue(filters.next() instanceof LogoutFilter);
        Object authProcFilter = filters.next();
        assertTrue(authProcFilter instanceof UsernamePasswordAuthenticationFilter);
        assertTrue(filters.next() instanceof DefaultLoginPageGeneratingFilter);
        assertTrue(filters.next() instanceof BasicAuthenticationFilter);
        assertTrue(filters.next() instanceof RequestCacheAwareFilter);
        assertTrue(filters.next() instanceof SecurityContextHolderAwareRequestFilter);
        assertTrue(filters.next() instanceof AnonymousAuthenticationFilter);
        assertTrue(filters.next() instanceof SessionManagementFilter);
        assertTrue(filters.next() instanceof ExceptionTranslationFilter);
        Object fsiObj = filters.next();
        assertTrue(fsiObj instanceof FilterSecurityInterceptor);
        FilterSecurityInterceptor fsi = (FilterSecurityInterceptor) fsiObj;
        assertTrue(fsi.isObserveOncePerRequest());
    }

    @Test
    public void filterListShouldBeEmptyForPatternWithNoFilters() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='/unprotected' filters='none' />" +
                "    </http>" + AUTH_PROVIDER_XML);

        List<Filter> filters = getFilters("/unprotected");

        assertTrue(filters.size() == 0);
    }

    @Test
    public void filtersEqualsNoneSupportsPlaceholderForPattern() throws Exception {
        System.setProperty("pattern.nofilters", "/unprotected");
        setContext(
                "    <b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='${pattern.nofilters}' filters='none' />" +
                "        <intercept-url pattern='/**' access='ROLE_A' />" +
                "    </http>" + AUTH_PROVIDER_XML);

        List<Filter> filters = getFilters("/unprotected");

        assertTrue(filters.size() == 0);
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void filtersEqualsNoneErrorsWithRequiresChannel() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='/ambiguousConfig' requires-channel='https' filters='none' />" +
                "    </http>" + AUTH_PROVIDER_XML);
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void filtersEqualsNoneErrorsWithAccess() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='/ambiguousConfig' access='ROLE_USER' filters='none' />" +
                "    </http>" + AUTH_PROVIDER_XML);
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void filtersEqualsNoneErrorsWithRequiresChannelAndAccess() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='/ambiguousConfig' requires-channel='https' filters='none' />" +
                "    </http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void regexPathsWorkCorrectly() throws Exception {
        setContext(
                "    <http auto-config='true' path-type='regex'>" +
                "        <intercept-url pattern='\\A\\/[a-z]+' filters='none' />" +
                "    </http>" + AUTH_PROVIDER_XML);
        assertEquals(0, getFilters("/imlowercase").size());
        // This will be matched by the default pattern ".*"
        List<Filter> allFilters = getFilters("/ImCaughtByTheUniversalMatchPattern");
        checkAutoConfigFilters(allFilters);
        assertEquals(false, FieldUtils.getFieldValue(appContext.getBean(BeanIds.FILTER_CHAIN_PROXY), "stripQueryStringFromUrls"));
        assertEquals(false, FieldUtils.getFieldValue(allFilters.get(AUTO_CONFIG_FILTERS-1), "securityMetadataSource.stripQueryStringFromUrls"));
    }

    @Test
    public void lowerCaseComparisonAttributeIsRespectedByFilterChainProxy() throws Exception {
        setContext(
                "    <http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
                "        <intercept-url pattern='/Secure*' filters='none' />" +
                "    </http>" + AUTH_PROVIDER_XML);
        assertEquals(0, getFilters("/Secure").size());
        // These will be matched by the default pattern "/**"
        checkAutoConfigFilters(getFilters("/secure"));
        checkAutoConfigFilters(getFilters("/ImCaughtByTheUniversalMatchPattern"));
    }

    @Test
    public void formLoginWithNoLoginPageAddsDefaultLoginPageFilter() throws Exception {
        setContext(
                "<http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
                "   <form-login />" +
                "</http>" + AUTH_PROVIDER_XML);
        // These will be matched by the default pattern "/**"
        checkAutoConfigFilters(getFilters("/anything"));
    }

    @Test
    public void formLoginAlwaysUseDefaultSetsCorrectProperty() throws Exception {
        setContext(
                "<http>" +
                "   <form-login default-target-url='/default' always-use-default-target='true' />" +
                "</http>" + AUTH_PROVIDER_XML);
        // These will be matched by the default pattern "/**"
        UsernamePasswordAuthenticationFilter filter = getFilter(UsernamePasswordAuthenticationFilter.class);
        assertEquals("/default", FieldUtils.getFieldValue(filter, "successHandler.defaultTargetUrl"));
        assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "successHandler.alwaysUseDefaultTargetUrl"));
    }

    // SEC-1152
    @Test
    public void anonymousFilterIsAddedByDefault() throws Exception {
        setContext(
                "<http>" +
                "   <form-login />" +
                "</http>" + AUTH_PROVIDER_XML);
        assertThat(getFilters("/anything").get(5), instanceOf(AnonymousAuthenticationFilter.class));
    }

    @Test
    public void anonymousFilterIsRemovedIfDisabledFlagSet() throws Exception {
        setContext(
                "<http>" +
                "   <form-login />" +
                "   <anonymous enabled='false'/>" +
                "</http>" + AUTH_PROVIDER_XML);
        assertThat(getFilters("/anything").get(5), not(instanceOf(AnonymousAuthenticationFilter.class)));
    }

    @Test
    public void anonymousCustomAttributesAreSetCorrectly() throws Exception {
        setContext(
                "<http>" +
                "   <form-login />" +
                "   <anonymous enabled='true' username='joe' granted-authority='anonymity' key='customKey' />" +
                "</http>" + AUTH_PROVIDER_XML);
        AnonymousAuthenticationFilter filter = getFilter(AnonymousAuthenticationFilter.class);
        assertEquals("customKey", filter.getKey());
        assertEquals("joe", filter.getUserAttribute().getPassword());
        assertEquals("anonymity", filter.getUserAttribute().getAuthorities().get(0).getAuthority());
    }

    @Test(expected=BeanCreationException.class)
    public void invalidLoginPageIsDetected() throws Exception {
        setContext(
                "<http>" +
                "   <form-login login-page='noLeadingSlash'/>" +
                "</http>" + AUTH_PROVIDER_XML);
    }

    @Test(expected=BeanCreationException.class)
    public void invalidDefaultTargetUrlIsDetected() throws Exception {
        setContext(
                "<http>" +
                "   <form-login default-target-url='noLeadingSlash'/>" +
                "</http>" + AUTH_PROVIDER_XML);
    }

    @Test(expected=BeanCreationException.class)
    public void invalidLogoutUrlIsDetected() throws Exception {
        setContext(
                "<http>" +
                "   <logout logout-url='noLeadingSlash'/>" +
                "   <form-login />" +
                "</http>" + AUTH_PROVIDER_XML);
    }

    @Test(expected=BeanCreationException.class)
    public void invalidLogoutSuccessUrlIsDetected() throws Exception {
        setContext(
                "<http>" +
                "   <logout logout-success-url='noLeadingSlash'/>" +
                "   <form-login />" +
                "</http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void logoutSuccessHandlerIsSetCorrectly() throws Exception {
        setContext(
                "<http>" +
                "   <logout success-handler-ref='logoutHandler' />" +
                "   <form-login />" +
                "</http>" +
                "<b:bean id='logoutHandler' class='org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler' />"
                + AUTH_PROVIDER_XML);

        LogoutFilter filter = (LogoutFilter) getFilter(LogoutFilter.class);
        LogoutSuccessHandler handler = (LogoutSuccessHandler) FieldUtils.getFieldValue(filter, "logoutSuccessHandler");
        assertSame(appContext.getBean("logoutHandler"), handler);
    }

    @Test
    public void lowerCaseComparisonIsRespectedBySecurityFilterInvocationDefinitionSource() throws Exception {
        setContext(
                "    <http auto-config='true' path-type='ant' lowercase-comparisons='false'>" +
                "        <intercept-url pattern='/Secure*' access='ROLE_A, ROLE_B' />" +
                "        <intercept-url pattern='/**' access='ROLE_C' />" +
                "    </http>" + AUTH_PROVIDER_XML);

        FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);

        FilterInvocationSecurityMetadataSource fids = fis.getSecurityMetadataSource();
        Collection<ConfigAttribute> attrDef = fids.getAttributes(createFilterinvocation("/Secure", null));
        assertEquals(2, attrDef.size());
        assertTrue(attrDef.contains(new SecurityConfig("ROLE_A")));
        assertTrue(attrDef.contains(new SecurityConfig("ROLE_B")));
        attrDef = fids.getAttributes(createFilterinvocation("/secure", null));
        assertEquals(1, attrDef.size());
        assertTrue(attrDef.contains(new SecurityConfig("ROLE_C")));
    }

    // SEC-1201
    @Test
    public void interceptUrlsAndFormLoginSupportPropertyPlaceholders() throws Exception {
        System.setProperty("secure.Url", "/Secure");
        System.setProperty("secure.role", "ROLE_A");
        System.setProperty("login.page", "/loginPage");
        System.setProperty("default.target", "/defaultTarget");
        System.setProperty("auth.failure", "/authFailure");
        setContext(
                "<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "<http>" +
                "    <intercept-url pattern='${secure.Url}' access='${secure.role}' />" +
                "    <intercept-url pattern='${login.page}' filters='none' />" +
                "    <form-login login-page='${login.page}' default-target-url='${default.target}' " +
                "        authentication-failure-url='${auth.failure}' />" +
                "</http>" + AUTH_PROVIDER_XML);
        checkPropertyValues();
        assertEquals(0, getFilters("/loginPage").size());
    }

    // SEC-1309
    @Test
    public void interceptUrlsAndFormLoginSupportEL() throws Exception {
        System.setProperty("secure.url", "/Secure");
        System.setProperty("secure.role", "ROLE_A");
        System.setProperty("login.page", "/loginPage");
        System.setProperty("default.target", "/defaultTarget");
        System.setProperty("auth.failure", "/authFailure");
        setContext(
                "<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "<http>" +
                "    <intercept-url pattern=\"#{systemProperties['secure.url']}\" access=\"#{systemProperties['secure.role']}\" />" +
                "    <form-login login-page=\"#{systemProperties['login.page']}\" default-target-url=\"#{systemProperties['default.target']}\" " +
                "        authentication-failure-url=\"#{systemProperties['auth.failure']}\" />" +
                "</http>" + AUTH_PROVIDER_XML);
        checkPropertyValues() ;
    }

    private void checkPropertyValues() throws Exception {
        // Check the security attribute
        FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);
        FilterInvocationSecurityMetadataSource fids = fis.getSecurityMetadataSource();
        Collection<ConfigAttribute> attrs = fids.getAttributes(createFilterinvocation("/secure", null));
        assertNotNull(attrs);
        assertEquals(1, attrs.size());
        assertTrue(attrs.contains(new SecurityConfig("ROLE_A")));

        // Check the form login properties are set
        UsernamePasswordAuthenticationFilter apf = getFilter(UsernamePasswordAuthenticationFilter.class);
        assertEquals("/defaultTarget", FieldUtils.getFieldValue(apf, "successHandler.defaultTargetUrl"));
        assertEquals("/authFailure", FieldUtils.getFieldValue(apf, "failureHandler.defaultFailureUrl"));

        ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);
        assertEquals("/loginPage", FieldUtils.getFieldValue(etf, "authenticationEntryPoint.loginFormUrl"));
    }

    @Test
    public void httpMethodMatchIsSupported() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='/**' access='ROLE_C' />" +
                "        <intercept-url pattern='/secure*' method='DELETE' access='ROLE_SUPERVISOR' />" +
                "        <intercept-url pattern='/secure*' method='POST' access='ROLE_A,ROLE_B' />" +
                "    </http>" + AUTH_PROVIDER_XML);

        FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);
        FilterInvocationSecurityMetadataSource fids = fis.getSecurityMetadataSource();
        Collection<ConfigAttribute> attrs = fids.getAttributes(createFilterinvocation("/secure", "POST"));
        assertEquals(2, attrs.size());
        assertTrue(attrs.contains(new SecurityConfig("ROLE_A")));
        assertTrue(attrs.contains(new SecurityConfig("ROLE_B")));
    }


    @Test
    public void httpMethodMatchIsSupportedForRequiresChannel() throws Exception {
        setContext(
                 "    <http auto-config='true'>" +
                 "        <intercept-url pattern='/anyurl'/>" +
                 "        <intercept-url pattern='/anyurl' method='GET' access='ROLE_ADMIN' requires-channel='https' />" +
                 "    </http>" + AUTH_PROVIDER_XML);

        ChannelProcessingFilter filter = getFilter(ChannelProcessingFilter.class);
        FilterInvocationSecurityMetadataSource fids = (FilterInvocationSecurityMetadataSource)FieldUtils.getFieldValue(filter,"securityMetadataSource");
        Collection<ConfigAttribute> attrs = fids.getAttributes(createFilterinvocation("/anyurl", "GET"));
         assertEquals(1, attrs.size());
        attrs = fids.getAttributes(createFilterinvocation("/anyurl", "POST"));
         assertEquals(null, attrs);
    }

    @Test
    public void oncePerRequestAttributeIsSupported() throws Exception {
        setContext("<http once-per-request='false'><http-basic /></http>" + AUTH_PROVIDER_XML);

        FilterSecurityInterceptor fsi = getFilter(FilterSecurityInterceptor.class);

        assertFalse(fsi.isObserveOncePerRequest());
    }

    @Test
    public void httpBasicSupportsSeparateEntryPoint() throws Exception {
        setContext("<http><http-basic entry-point-ref='ep' /></http>" +
                   "<b:bean id='ep' class='org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint'>" +
                   "    <b:property name='realmName' value='whocares'/>" +
                   "</b:bean>" + AUTH_PROVIDER_XML);

        BasicAuthenticationFilter baf = getFilter(BasicAuthenticationFilter.class);
        assertSame(appContext.getBean("ep"), FieldUtils.getFieldValue(baf, "authenticationEntryPoint"));
        // Since no other authentication system is in use, this should also end up on the ETF
        ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);
        assertSame(appContext.getBean("ep"), FieldUtils.getFieldValue(etf, "authenticationEntryPoint"));
    }


    @Test
    public void accessDeniedPageAttributeIsSupported() throws Exception {
        setContext("<http access-denied-page='/access-denied'><http-basic /></http>" + AUTH_PROVIDER_XML);

        ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);

        assertEquals("/access-denied", FieldUtils.getFieldValue(etf, "accessDeniedHandler.errorPage"));
    }

    @Test(expected=BeanCreationException.class)
    public void invalidAccessDeniedUrlIsDetected() throws Exception {
        setContext("<http auto-config='true' access-denied-page='noLeadingSlash'/>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void interceptUrlWithRequiresChannelAddsChannelFilterToStack() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='/**' requires-channel='https' />" +
                "    </http>" + AUTH_PROVIDER_XML);
        List<Filter> filters = getFilters("/someurl");

        assertEquals("Expected " + (AUTO_CONFIG_FILTERS + 1) +"  filters in chain", AUTO_CONFIG_FILTERS + 1, filters.size());

        assertTrue(filters.get(0) instanceof ChannelProcessingFilter);
    }

    @Test
    public void requiresChannelSupportsPlaceholder() throws Exception {
        System.setProperty("secure.url", "/secure");
        System.setProperty("required.channel", "https");
        setContext(
                "    <b:bean id='configurer' class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "    <http auto-config='true'>" +
                "        <intercept-url pattern='${secure.url}' requires-channel='${required.channel}' />" +
                "    </http>" + AUTH_PROVIDER_XML);
        List<Filter> filters = getFilters("/secure");

        assertTrue(filters.get(0) instanceof ChannelProcessingFilter);
        ChannelProcessingFilter filter = (ChannelProcessingFilter) filters.get(0);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setServletPath("/secure");
        MockHttpServletResponse response = new MockHttpServletResponse();
        filter.doFilter(request, response, new MockFilterChain());
        assertNotNull(response.getRedirectedUrl());
        assertTrue(response.getRedirectedUrl().startsWith("https"));
    }

    @Test
    public void portMappingsAreParsedCorrectly() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <port-mappings>" +
                "            <port-mapping http='9080' https='9443'/>" +
                "        </port-mappings>" +
                "    </http>" + AUTH_PROVIDER_XML);

        PortMapperImpl pm = getPortMapper();
        assertEquals(1, pm.getTranslatedPortMappings().size());
        assertEquals(Integer.valueOf(9080), pm.lookupHttpPort(9443));
        assertEquals(Integer.valueOf(9443), pm.lookupHttpsPort(9080));
    }

    @Test
    public void portMappingsWorkWithPlaceholdersAndEL() throws Exception {
        System.setProperty("http", "9080");
        System.setProperty("https", "9443");
        setContext(
                "    <b:bean id='configurer' class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "    <http auto-config='true'>" +
                "        <port-mappings>" +
                "            <port-mapping http='#{systemProperties.http}' https='${https}'/>" +
                "        </port-mappings>" +
                "    </http>" + AUTH_PROVIDER_XML);

        PortMapperImpl pm = getPortMapper();
        assertEquals(1, pm.getTranslatedPortMappings().size());
        assertEquals(Integer.valueOf(9080), pm.lookupHttpPort(9443));
        assertEquals(Integer.valueOf(9443), pm.lookupHttpsPort(9080));
    }

    private PortMapperImpl getPortMapper() {
        Map<String,PortMapperImpl> beans = appContext.getBeansOfType(PortMapperImpl.class);
        return new ArrayList<PortMapperImpl>(beans.values()).get(0);
    }

    @Test
    public void accessDeniedPageWorksWithPlaceholders() throws Exception {
        System.setProperty("accessDenied", "/go-away");
        setContext(
                "    <b:bean id='configurer' class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "    <http auto-config='true' access-denied-page='${accessDenied}'/>" + AUTH_PROVIDER_XML);
        ExceptionTranslationFilter filter = (ExceptionTranslationFilter) getFilter(ExceptionTranslationFilter.class);
        assertEquals("/go-away", FieldUtils.getFieldValue(filter, "accessDeniedHandler.errorPage"));
    }

    @Test
    public void accessDeniedHandlerPageWorksWithEL() throws Exception {
        setContext(
                "    <http auto-config='true'>" +
                "        <access-denied-handler error-page=\"#{'/go' + '-away'} \" />" +
                "    </http>" + AUTH_PROVIDER_XML);
        ExceptionTranslationFilter filter = getFilter(ExceptionTranslationFilter.class);
        assertEquals("/go-away", FieldUtils.getFieldValue(filter, "accessDeniedHandler.errorPage"));
    }

    @Test
    public void accessDeniedHandlerIsSetCorectly() throws Exception {
        setContext(
                "    <b:bean id='adh' class='" + AccessDeniedHandlerImpl.class.getName() + "'/>" +
                "    <http auto-config='true'>" +
                "        <access-denied-handler ref='adh'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
        ExceptionTranslationFilter filter = getFilter(ExceptionTranslationFilter.class);
        AccessDeniedHandlerImpl adh = (AccessDeniedHandlerImpl) appContext.getBean("adh");
        assertSame(adh, FieldUtils.getFieldValue(filter, "accessDeniedHandler"));
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void accessDeniedPageAndAccessDeniedHandlerAreMutuallyExclusive() throws Exception {
        setContext(
                "    <http auto-config='true' access-denied-page='/go-away'>" +
                "        <access-denied-handler error-page='/go-away'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
        ExceptionTranslationFilter filter = getFilter(ExceptionTranslationFilter.class);
        assertEquals("/go-away", FieldUtils.getFieldValue(filter, "accessDeniedHandler.errorPage"));
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void accessDeniedHandlerPageAndRefAreMutuallyExclusive() throws Exception {
        setContext(
                "    <b:bean id='adh' class='" + AccessDeniedHandlerImpl.class.getName() + "'/>" +
                "    <http auto-config='true'>" +
                "        <access-denied-handler error-page='/go-away' ref='adh'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
        ExceptionTranslationFilter filter = getFilter(ExceptionTranslationFilter.class);
        assertEquals("/go-away", FieldUtils.getFieldValue(filter, "accessDeniedHandler.errorPage"));
    }

    @Test
    public void externalFiltersAreTreatedCorrectly() throws Exception {
        // Decorated user-filters should be added to stack. The others should be ignored.
        String contextHolderFilterClass = SecurityContextHolderAwareRequestFilter.class.getName();
        String contextPersistenceFilterClass = SecurityContextPersistenceFilter.class.getName();
        System.setProperty("customFilterRef", "userFilter1");

        setContext(
                "<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "<http auto-config='true'>" +
                "    <custom-filter position='FIRST' ref='${customFilterRef}' />" +
                "    <custom-filter after='LOGOUT_FILTER' ref='userFilter' />" +
                "    <custom-filter before='SECURITY_CONTEXT_FILTER' ref='userFilter3'/>" +
                "</http>" + AUTH_PROVIDER_XML +
                "<b:bean id='userFilter' class='"+ contextHolderFilterClass +"'/>" +
                "<b:bean id='userFilter1' class='" + contextPersistenceFilterClass + "'/>" +
                "<b:bean id='userFilter2' class='" + contextPersistenceFilterClass + "'/>" +
                "<b:bean id='userFilter3' class='" + contextPersistenceFilterClass + "'/>" +
                "<b:bean id='userFilter4' class='"+ contextHolderFilterClass +"'/>"
                );
        List<Filter> filters = getFilters("/someurl");

        assertEquals(AUTO_CONFIG_FILTERS + 3, filters.size());
        assertTrue(filters.get(0) instanceof SecurityContextPersistenceFilter);
        assertTrue(filters.get(1) instanceof SecurityContextPersistenceFilter);
        assertTrue(filters.get(4) instanceof SecurityContextHolderAwareRequestFilter);
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void twoFiltersWithSameOrderAreRejected() {
        setContext(
                "<http auto-config='true'>" +
                "    <custom-filter position='LOGOUT_FILTER' ref='userFilter'/>" +
                "</http>" + AUTH_PROVIDER_XML +
                "<b:bean id='userFilter' class='" + SecurityContextHolderAwareRequestFilter.class.getName() + "'/>");
    }

    @Test
    public void rememberMeServiceWorksWithTokenRepoRef() throws Exception {
        setContext(
            "<http auto-config='true'>" +
            "    <remember-me token-repository-ref='tokenRepo'/>" +
            "</http>" +
            "<b:bean id='tokenRepo' " +
                    "class='" + InMemoryTokenRepositoryImpl.class.getName() + "'/> " + AUTH_PROVIDER_XML);
        RememberMeServices rememberMeServices = getRememberMeServices();

        assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices);
        assertFalse((Boolean)FieldUtils.getFieldValue(getRememberMeServices(), "useSecureCookie"));
    }

    @Test
    public void rememberMeServiceWorksWithDataSourceRef() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <remember-me data-source-ref='ds'/>" +
                "</http>" +
                "<b:bean id='ds' class='org.springframework.security.TestDataSource'> " +
                "    <b:constructor-arg value='tokendb'/>" +
                "</b:bean>" + AUTH_PROVIDER_XML);
        RememberMeServices rememberMeServices = getRememberMeServices();

        assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices);
    }

    @Test
    @SuppressWarnings("unchecked")
    public void rememberMeServiceWorksWithExternalServicesImpl() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <remember-me key=\"#{'our' + 'key'}\" services-ref='rms'/>" +
                "</http>" +
                "<b:bean id='rms' class='"+ TokenBasedRememberMeServices.class.getName() +"'> " +
                "    <b:property name='userDetailsService' ref='us'/>" +
                "    <b:property name='key' value='ourkey' />" +
                "    <b:property name='tokenValiditySeconds' value='5000'/>" +
                "</b:bean>" +
                AUTH_PROVIDER_XML);

        assertEquals(5000, FieldUtils.getFieldValue(getRememberMeServices(), "tokenValiditySeconds"));
        // SEC-909
        List<LogoutHandler> logoutHandlers = (List<LogoutHandler>) FieldUtils.getFieldValue(getFilter(LogoutFilter.class), "handlers");
        assertEquals(2, logoutHandlers.size());
        assertEquals(getRememberMeServices(), logoutHandlers.get(1));
        // SEC-1281
        Map ams = appContext.getBeansOfType(ProviderManager.class);
        ams.remove(BeanIds.AUTHENTICATION_MANAGER);
        RememberMeAuthenticationProvider rmp = (RememberMeAuthenticationProvider) ((ProviderManager)ams.values().toArray()[0]).getProviders().get(1);
        assertEquals("ourkey", rmp.getKey());
    }

    @Test
    public void rememberMeTokenValidityIsParsedCorrectly() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <remember-me key='ourkey' token-validity-seconds='10000' />" +
                "</http>" + AUTH_PROVIDER_XML);
        assertEquals(10000, FieldUtils.getFieldValue(getRememberMeServices(), "tokenValiditySeconds"));
    }

    @Test
    public void rememberMeTokenValidityAllowsNegativeValueForNonPersistentImplementation() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <remember-me key='ourkey' token-validity-seconds='-1' />" +
                "</http>" + AUTH_PROVIDER_XML);
        assertEquals(-1, FieldUtils.getFieldValue(getRememberMeServices(), "tokenValiditySeconds"));
    }

    @Test
    public void rememberMeSecureCookieAttributeIsSetCorrectly() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <remember-me key='ourkey' use-secure-cookie='true' />" +
                "</http>" + AUTH_PROVIDER_XML);
        assertTrue((Boolean)FieldUtils.getFieldValue(getRememberMeServices(), "useSecureCookie"));
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void rememberMeTokenValidityRejectsNegativeValueForPersistentImplementation() throws Exception {
        setContext(
            "<http auto-config='true'>" +
            "    <remember-me token-validity-seconds='-1' token-repository-ref='tokenRepo'/>" +
            "</http>" +
            "<b:bean id='tokenRepo' class='org.springframework.security.ui.rememberme.InMemoryTokenRepositoryImpl'/> " +
                    AUTH_PROVIDER_XML);
    }

    @Test
    public void rememberMeServiceConfigurationParsesWithCustomUserService() {
        setContext(
                "<http auto-config='true'>" +
                "    <remember-me key='somekey' user-service-ref='userService'/>" +
                "</http>" +
                "<b:bean id='userService' class='org.springframework.security.core.userdetails.MockUserDetailsService'/> " +
                AUTH_PROVIDER_XML);
//        AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
    }

    @Test
    public void x509SupportAddsFilterAtExpectedPosition() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <x509 />" +
                "</http>"  + AUTH_PROVIDER_XML);
        List<Filter> filters = getFilters("/someurl");

        assertTrue(filters.get(2) instanceof X509AuthenticationFilter);
    }

    @Test
    public void x509SubjectPrincipalRegexCanBeSetUsingPropertyPlaceholder() throws Exception {
        System.setProperty("subject-principal-regex", "uid=(.*),");
        setContext(
                "<b:bean class='org.springframework.beans.factory.config.PropertyPlaceholderConfigurer'/>" +
                "<http auto-config='true'>" +
                "    <x509 subject-principal-regex='${subject-principal-regex}'/>" +
                "</http>"  + AUTH_PROVIDER_XML);

        X509AuthenticationFilter filter = getFilter(X509AuthenticationFilter.class);
        SubjectDnX509PrincipalExtractor pe = (SubjectDnX509PrincipalExtractor) FieldUtils.getFieldValue(filter, "principalExtractor");
        Pattern p = (Pattern) FieldUtils.getFieldValue(pe, "subjectDnPattern");
        assertEquals("uid=(.*),", p.pattern());
    }

    @Test
    public void concurrentSessionSupportAddsFilterAndExpectedBeans() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <session-management>" +
                "        <concurrency-control session-registry-alias='sr' expired-url='/expired'/>" +
                "    </session-management>" +
                "</http>" + AUTH_PROVIDER_XML);
        List<Filter> filters = getFilters("/someurl");

        assertTrue(filters.get(0) instanceof ConcurrentSessionFilter);
        assertNotNull(appContext.getBean("sr"));
        SessionManagementFilter smf = getFilter(SessionManagementFilter.class);
        assertNotNull(smf);
        checkSessionRegistry();
    }

    @Test
    public void externalSessionStrategyIsSupported() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <session-management session-authentication-strategy-ref='ss'/>" +
                "</http>" +
                "<b:bean id='ss' class='org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy'/>"
                + AUTH_PROVIDER_XML);
        //session-authentication-strategy-ref
    }

    @Test
    public void externalSessionRegistryBeanIsConfiguredCorrectly() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <session-management>" +
                "        <concurrency-control session-registry-ref='sr' />" +
                "    </session-management>" +
                "</http>" +
                "<b:bean id='sr' class='" + SessionRegistryImpl.class.getName() + "'/>" +
                AUTH_PROVIDER_XML);
        checkSessionRegistry();
    }

    private void checkSessionRegistry() throws Exception {
        Object sessionRegistry = appContext.getBean("sr");
        Object sessionRegistryFromConcurrencyFilter = FieldUtils.getFieldValue(
                getFilter(ConcurrentSessionFilter.class), "sessionRegistry");
        Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue(
                getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry");
//        Object sessionRegistryFromController = FieldUtils.getFieldValue(getConcurrentSessionController(),"sessionRegistry");
        Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue(
                getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry");

        assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter);
//        assertSame(sessionRegistry, sessionRegistryFromController);
        assertSame(sessionRegistry, sessionRegistryFromMgmtFilter);
        // SEC-1143
        assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
    }

    @Test
    public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <session-management session-authentication-error-url='/max-exceeded'>" +
                "        <concurrency-control max-sessions='2' error-if-maximum-exceeded='true' />" +
                "    </session-management>" +
                "</http>"  + AUTH_PROVIDER_XML);
        SessionManagementFilter seshFilter = getFilter(SessionManagementFilter.class);
        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
        SecurityContextHolder.getContext().setAuthentication(auth);
        // Register 2 sessions and then check a third
//        req.setSession(new MockHttpSession());
//        auth.setDetails(new WebAuthenticationDetails(req));
        MockHttpServletResponse mockResponse = new MockHttpServletResponse();
        SaveContextOnUpdateOrErrorResponseWrapper response = new SaveContextOnUpdateOrErrorResponseWrapper(mockResponse, false) {
            protected void saveContext(SecurityContext context) {
            }
        };
        seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
        assertNull(mockResponse.getRedirectedUrl());
        seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
        assertNull(mockResponse.getRedirectedUrl());
        seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
        assertEquals("/max-exceeded", mockResponse.getRedirectedUrl());
    }

    @Test
    public void externalRequestCacheIsConfiguredCorrectly() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <request-cache ref='cache' />" +
                "</http>" +
                "<b:bean id='cache' class='" + HttpSessionRequestCache.class.getName() + "'/>" +
                AUTH_PROVIDER_XML);
        Object requestCache = appContext.getBean("cache");
        assertSame(requestCache, FieldUtils.getFieldValue(getFilter(ExceptionTranslationFilter.class), "requestCache"));
    }

    @Test
    public void customEntryPointIsSupported() throws Exception {
        setContext(
                "<http auto-config='true' entry-point-ref='entryPoint'/>" +
                "<b:bean id='entryPoint' class='" + MockEntryPoint.class.getName() + "'>" +
                "</b:bean>" + AUTH_PROVIDER_XML);
        assertTrue("ExceptionTranslationFilter should be configured with custom entry point",
                getFilter(ExceptionTranslationFilter.class).getAuthenticationEntryPoint() instanceof MockEntryPoint);
    }

    @SuppressWarnings("unused")
    private static class MockEntryPoint extends LoginUrlAuthenticationEntryPoint {
        public MockEntryPoint() {
            super.setLoginFormUrl("/notused");
        }
    }

    @Test
    /** SEC-742 */
    public void rememberMeServicesWorksWithoutBasicProcessingFilter() {
        setContext(
                "    <http>" +
                "        <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
                "        <logout logout-success-url='/login.jsp'/>" +
                "        <anonymous username='guest' granted-authority='guest'/>" +
                "        <remember-me />" +
                "    </http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void disablingSessionProtectionRemovesSessionManagementFilterIfNoInvalidSessionUrlSet() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <session-management session-fixation-protection='none'/>" +
                "</http>" + AUTH_PROVIDER_XML);
        List<Filter> filters = getFilters("/someurl");
        assertFalse(filters.get(8) instanceof SessionManagementFilter);
    }

    @Test
    public void disablingSessionProtectionRetainsSessionManagementFilterInvalidSessionUrlSet() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <session-management session-fixation-protection='none' invalid-session-url='/timeoutUrl'/>" +
                "</http>" + AUTH_PROVIDER_XML);
        List<Filter> filters = getFilters("/someurl");
        Object filter = filters.get(8);
        assertTrue(filter instanceof SessionManagementFilter);
        assertEquals("/timeoutUrl", FieldUtils.getProtectedFieldValue("invalidSessionUrl", filter));
    }

    /**
     * See SEC-750. If the http security post processor causes beans to be instantiated too eagerly, they way miss
     * additional processing. In this method we have a UserDetailsService which is referenced from the namespace
     * and also has a post processor registered which will modify it.
     */
    @Test
    public void httpElementDoesntInterfereWithBeanPostProcessing() {
        setContext(
                "<http auto-config='true'/>" +
                "<authentication-manager>" +
                "   <authentication-provider user-service-ref='myUserService'/>" +
                "</authentication-manager>" +
                "<b:bean id='myUserService' class='org.springframework.security.config.PostProcessedMockUserDetailsService'/>" +
                "<b:bean id='beanPostProcessor' class='org.springframework.security.config.MockUserServiceBeanPostProcessor'/>"
        );

        PostProcessedMockUserDetailsService service = (PostProcessedMockUserDetailsService)appContext.getBean("myUserService");

        assertEquals("Hello from the post processor!", service.getPostProcessorWasHere());
    }

    /**
     * SEC-795. Two methods that exercise the scenarios that will or won't result in a protected login page warning.
     * Check the log.
     */
    @Test
    public void unprotectedLoginPageDoesntResultInWarning() {
        // Anonymous access configured
        setContext(
                "    <http>" +
                "        <intercept-url pattern='/login.jsp*' access='IS_AUTHENTICATED_ANONYMOUSLY'/>" +
                "        <intercept-url pattern='/**' access='ROLE_A'/>" +
                "        <anonymous />" +
                "        <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
        closeAppContext();
        // No filters applied to login page
        setContext(
                "    <http>" +
                "        <intercept-url pattern='/login.jsp*' filters='none'/>" +
                "        <intercept-url pattern='/**' access='ROLE_A'/>" +
                "        <anonymous />" +
                "        <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void protectedLoginPageResultsInWarning() {
        // Protected, no anonymous filter configured.
        setContext(
                "    <http>" +
                "        <intercept-url pattern='/**' access='ROLE_A'/>" +
                "        <anonymous enabled='false' />" +
                "        <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
        closeAppContext();
        // Protected, anonymous provider but no access
        setContext(
                "    <http>" +
                "        <intercept-url pattern='/**' access='ROLE_A'/>" +
                "        <anonymous />" +
                "        <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
                "    </http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void settingCreateSessionToAlwaysSetsFilterPropertiesCorrectly() throws Exception {
        setContext("<http auto-config='true' create-session='always'/>" + AUTH_PROVIDER_XML);
        Object filter = getFilter(SecurityContextPersistenceFilter.class);

        assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "forceEagerSessionCreation"));
        assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "repo.allowSessionCreation"));
        // Just check that the repo has url rewriting enabled by default
        assertEquals(Boolean.FALSE, FieldUtils.getFieldValue(filter, "repo.disableUrlRewriting"));
    }

    @Test
    public void settingCreateSessionToNeverSetsFilterPropertiesCorrectly() throws Exception {
        setContext("<http auto-config='true' create-session='never'/>" + AUTH_PROVIDER_XML);
        Object filter = getFilter(SecurityContextPersistenceFilter.class);
        assertEquals(Boolean.FALSE, FieldUtils.getFieldValue(filter, "forceEagerSessionCreation"));
        assertEquals(Boolean.FALSE, FieldUtils.getFieldValue(filter, "repo.allowSessionCreation"));
        // Check that an invocation doesn't create a session
        FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setServletPath("/anything");
        fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
        assertNull(request.getSession(false));
    }

    @Test
    public void settingCreateSessionToIfRequiredDoesntCreateASessionForPublicInvocation() throws Exception {
        setContext("<http auto-config='true' create-session='ifRequired'/>" + AUTH_PROVIDER_XML);
        Object filter = getFilter(SecurityContextPersistenceFilter.class);
        assertEquals(Boolean.FALSE, FieldUtils.getFieldValue(filter, "forceEagerSessionCreation"));
        assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "repo.allowSessionCreation"));
        // Check that an invocation doesn't create a session
        FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setServletPath("/anything");
        fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
        assertNull(request.getSession(false));
    }


    /* SEC-934 */
    @Test
    public void supportsTwoIdenticalInterceptUrls() throws Exception {
        setContext(
                "<http auto-config='true'>" +
                "    <intercept-url pattern='/someurl' access='ROLE_A'/>" +
                "    <intercept-url pattern='/someurl' access='ROLE_B'/>" +
                "</http>" + AUTH_PROVIDER_XML);
        FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);

        FilterInvocationSecurityMetadataSource fids = fis.getSecurityMetadataSource();
        Collection<ConfigAttribute> attrDef = fids.getAttributes(createFilterinvocation("/someurl", null));
        assertEquals(1, attrDef.size());
        assertTrue(attrDef.contains(new SecurityConfig("ROLE_B")));
    }

    @Test
    public void supportsExternallyDefinedSecurityContextRepository() throws Exception {
        setContext(
                "<b:bean id='repo' class='" + HttpSessionSecurityContextRepository.class.getName() + "'/>" +
                "<http create-session='always' security-context-repository-ref='repo'>" +
                "    <http-basic />" +
                "</http>" + AUTH_PROVIDER_XML);
        SecurityContextPersistenceFilter filter = getFilter(SecurityContextPersistenceFilter.class);;
        HttpSessionSecurityContextRepository repo = (HttpSessionSecurityContextRepository) appContext.getBean("repo");
        assertSame(repo, FieldUtils.getFieldValue(filter, "repo"));
        assertTrue((Boolean)FieldUtils.getFieldValue(filter, "forceEagerSessionCreation"));
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void cantUseUnsupportedSessionCreationAttributeWithExternallyDefinedSecurityContextRepository() throws Exception {
        setContext(
                "<b:bean id='repo' class='" + HttpSessionSecurityContextRepository.class.getName() + "'/>" +
                "<http create-session='never' security-context-repository-ref='repo'>" +
                "    <http-basic />" +
                "</http>" + AUTH_PROVIDER_XML);
    }

    @Test
    public void expressionBasedAccessAllowsAndDeniesAccessAsExpected() throws Exception {
        setContext(
                "    <http auto-config='true' use-expressions='true'>" +
                "        <intercept-url pattern='/secure*' access=\"hasAnyRole('ROLE_A','ROLE_C')\" />" +
                "        <intercept-url pattern='/**' access='permitAll()' />" +
                "    </http>" + AUTH_PROVIDER_XML);

        FilterSecurityInterceptor fis = getFilter(FilterSecurityInterceptor.class);

        FilterInvocationSecurityMetadataSource fids = fis.getSecurityMetadataSource();
        Collection<ConfigAttribute> attrDef = fids.getAttributes(createFilterinvocation("/secure", null));
        assertEquals(1, attrDef.size());

        // Try an unprotected invocation
        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_A"));
        fis.invoke(createFilterinvocation("/permitallurl", null));
        // Try secure Url as a valid user
        fis.invoke(createFilterinvocation("/securex", null));
        // And as a user without the required role
        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("joe", "", "ROLE_B"));
        try {
            fis.invoke(createFilterinvocation("/securex", null));
            fail("Expected AccessDeniedInvocation");
        } catch (AccessDeniedException expected) {
        }
    }

    @Test
    public void customSuccessAndFailureHandlersCanBeSetThroughTheNamespace() throws Exception {
        setContext(
                "<http>" +
                "   <form-login authentication-success-handler-ref='sh' authentication-failure-handler-ref='fh'/>" +
                "</http>" +
                "<b:bean id='sh' class='" + SavedRequestAwareAuthenticationSuccessHandler.class.getName() +"'/>" +
                "<b:bean id='fh' class='" + SimpleUrlAuthenticationFailureHandler.class.getName() + "'/>" +
                AUTH_PROVIDER_XML);
        UsernamePasswordAuthenticationFilter apf = getFilter(UsernamePasswordAuthenticationFilter.class);
        AuthenticationSuccessHandler sh = (AuthenticationSuccessHandler) appContext.getBean("sh");
        AuthenticationFailureHandler fh = (AuthenticationFailureHandler) appContext.getBean("fh");
        assertSame(sh, FieldUtils.getFieldValue(apf, "successHandler"));
        assertSame(fh, FieldUtils.getFieldValue(apf, "failureHandler"));
    }

    @Test
    public void disablingUrlRewritingThroughTheNamespaceSetsCorrectPropertyOnContextRepo() throws Exception {
        setContext("<http auto-config='true' disable-url-rewriting='true'/>" + AUTH_PROVIDER_XML);
        Object filter = getFilter(SecurityContextPersistenceFilter.class);
        assertEquals(Boolean.TRUE, FieldUtils.getFieldValue(filter, "repo.disableUrlRewriting"));
    }

    @Test
    public void userDetailsServiceInParentContextIsLocatedSuccessfully() throws Exception {
        appContext = new InMemoryXmlApplicationContext(AUTH_PROVIDER_XML);

        appContext = new InMemoryXmlApplicationContext(
                "<http auto-config='true'>" +
                "    <remember-me />" +
                "</http>", appContext);
    }

    @Test
    public void openIDAndFormLoginWorkTogether() throws Exception {
        setContext(
                "<http>" +
                "   <openid-login />" +
                "   <form-login />" +
                "</http>" +
                AUTH_PROVIDER_XML);
        ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);
        LoginUrlAuthenticationEntryPoint ap = (LoginUrlAuthenticationEntryPoint) etf.getAuthenticationEntryPoint();
        assertEquals("/spring_security_login", ap.getLoginFormUrl());
        // Default login filter should be present since we haven't specified any login URLs
        getFilter(DefaultLoginPageGeneratingFilter.class);
    }

    @Test
    public void openIDAndRememberMeWorkTogether() throws Exception {
        setContext(
                "<http>" +
                "   <intercept-url pattern='/**' access='ROLE_NOBODY'/>" +
                "   <openid-login />" +
                "   <remember-me />" +
                "</http>" +
                AUTH_PROVIDER_XML);
        // Default login filter should be present since we haven't specified any login URLs
        DefaultLoginPageGeneratingFilter loginFilter = getFilter(DefaultLoginPageGeneratingFilter.class);
        OpenIDAuthenticationFilter openIDFilter = getFilter(OpenIDAuthenticationFilter.class);
        openIDFilter.setConsumer(new OpenIDConsumer() {
            public String beginConsumption(HttpServletRequest req, String claimedIdentity, String returnToUrl, String realm)
                    throws OpenIDConsumerException {
                return "http://testopenid.com?openid.return_to=" + returnToUrl;
            }

            public OpenIDAuthenticationToken endConsumption(HttpServletRequest req) throws OpenIDConsumerException {
                throw new UnsupportedOperationException();
            }
        });
        Set<String> returnToUrlParameters = new HashSet<String>();
        returnToUrlParameters.add(AbstractRememberMeServices.DEFAULT_PARAMETER);
        openIDFilter.setReturnToUrlParameters(returnToUrlParameters);
        assertNotNull(FieldUtils.getFieldValue(loginFilter, "openIDrememberMeParameter"));
        MockHttpServletRequest request = new MockHttpServletRequest();
        MockHttpServletResponse response = new MockHttpServletResponse();

        FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
        request.setServletPath("/something.html");
        fcp.doFilter(request, response, new MockFilterChain());
        assertTrue(response.getRedirectedUrl().endsWith("/spring_security_login"));
        request.setServletPath("/spring_security_login");
        request.setRequestURI("/spring_security_login");
        response = new MockHttpServletResponse();
        fcp.doFilter(request, response, new MockFilterChain());
        assertTrue(response.getContentAsString().contains(AbstractRememberMeServices.DEFAULT_PARAMETER));
        request.setRequestURI("/j_spring_openid_security_check");
        request.setParameter(OpenIDAuthenticationFilter.DEFAULT_CLAIMED_IDENTITY_FIELD, "http://hey.openid.com/");
        request.setParameter(AbstractRememberMeServices.DEFAULT_PARAMETER, "on");
        response = new MockHttpServletResponse();
        fcp.doFilter(request, response, new MockFilterChain());
        String expectedReturnTo = request.getRequestURL().append("?")
                                        .append(AbstractRememberMeServices.DEFAULT_PARAMETER)
                                        .append("=").append("on").toString();
        assertEquals("http://testopenid.com?openid.return_to=" + expectedReturnTo, response.getRedirectedUrl());
    }

    @Test
    public void formLoginEntryPointTakesPrecedenceIfLoginUrlIsSet() throws Exception {
        setContext(
                "<http>" +
                "   <openid-login />" +
                "   <form-login login-page='/form_login_page' />" +
                "</http>" +
                AUTH_PROVIDER_XML);
        ExceptionTranslationFilter etf = getFilter(ExceptionTranslationFilter.class);
        LoginUrlAuthenticationEntryPoint ap = (LoginUrlAuthenticationEntryPoint) etf.getAuthenticationEntryPoint();
        assertEquals("/form_login_page", ap.getLoginFormUrl());
        try {
            getFilter(DefaultLoginPageGeneratingFilter.class);
            fail("Login page generating filter shouldn't be present");
        } catch (Exception expected) {
        }
    }

    @Test
    public void openIDEntryPointTakesPrecedenceIfLoginUrlIsSet() throws Exception {
        setContext(
                "<http>" +
                "   <openid-login login-page='/openid_login' />" +
                "   <form-login />" +
                "</http>" +
                AUTH_PROVIDER_XML);
        ExceptionTranslationFilter etf =  getFilter(ExceptionTranslationFilter.class);
        LoginUrlAuthenticationEntryPoint ap = (LoginUrlAuthenticationEntryPoint) etf.getAuthenticationEntryPoint();
        assertEquals("/openid_login", ap.getLoginFormUrl());
    }

    @SuppressWarnings("unchecked")
    @Test
    public void openIDWithAttributeExchangeConfigurationIsParsedCorrectly() throws Exception {
        setContext(
                "<http>" +
                "   <openid-login>" +
                "      <attribute-exchange>" +
                "          <openid-attribute name='nickname' type='http://schema.openid.net/namePerson/friendly'/>" +
                "          <openid-attribute name='email' type='http://schema.openid.net/contact/email' required='true' count='2'/>" +
                "      </attribute-exchange>" +
                "   </openid-login>" +
                "</http>" +
                AUTH_PROVIDER_XML);
        OpenIDAuthenticationFilter apf = getFilter(OpenIDAuthenticationFilter.class);

        OpenID4JavaConsumer consumer = (OpenID4JavaConsumer) FieldUtils.getFieldValue(apf, "consumer");
        List<OpenIDAttribute> attributes = (List<OpenIDAttribute>) FieldUtils.getFieldValue(consumer, "attributesToFetch");
        assertEquals(2, attributes.size());
        assertEquals("nickname", attributes.get(0).getName());
        assertEquals("http://schema.openid.net/namePerson/friendly", attributes.get(0).getType());
        assertFalse(attributes.get(0).isRequired());
        assertTrue(attributes.get(1).isRequired());
        assertEquals(2, attributes.get(1).getCount());
    }

    @Test(expected=BeanDefinitionParsingException.class)
    public void multipleLoginPagesCausesError() throws Exception {
        setContext(
                "<http>" +
                "   <openid-login login-page='/openid_login_page' />" +
                "   <form-login login-page='/form_login_page' />" +
                "</http>" +
                AUTH_PROVIDER_XML);
    }

    @Test
    public void httpConfigWithNoAuthProvidersWorksOk() throws Exception {
        setContext(
                "<http>" +
                "   <form-login />" +
                "   <anonymous enabled='false' />" +
                "</http>" +
                AUTH_PROVIDER_XML);
        FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
        MockHttpServletRequest request = new MockHttpServletRequest("POST", "/j_spring_security_check");
        request.setServletPath("/j_spring_security_check");
        request.addParameter("j_username", "bob");
        request.addParameter("j_password", "bob");
        fcp.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
    }

    @Test
    public void httpFirewallInjectionIsSupported() throws Exception {
        setContext(
                "<http-firewall ref='fw'/>" +
                "<http>" +
                "   <form-login />" +
                "</http>" +
                "<b:bean id='fw' class='" + DefaultHttpFirewall.class.getName() +"'/>" +
                AUTH_PROVIDER_XML);
        FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
        assertSame(appContext.getBean("fw"), FieldUtils.getFieldValue(fcp, "firewall"));
    }

    private void setContext(String context) {
        appContext = new InMemoryXmlApplicationContext(context);
    }

    @SuppressWarnings("unchecked")
    private <T extends Filter> List<T> getFilters(String url) throws Exception {
        FilterChainProxy fcp = (FilterChainProxy) appContext.getBean(BeanIds.FILTER_CHAIN_PROXY);
        Method getFilters = fcp.getClass().getDeclaredMethod("getFilters", String.class);
        getFilters.setAccessible(true);
        return (List<T>) ReflectionUtils.invokeMethod(getFilters, fcp, new Object[] {url});
    }

    private FilterInvocation createFilterinvocation(String path, String method) {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setMethod(method);
        request.setRequestURI(null);

        request.setServletPath(path);

        return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain());
    }

    private <T extends Filter> T getFilter(Class<T> type) throws Exception {
        List<T> filters = getFilters("/any");

        for (T f : filters) {
            if (f.getClass().isAssignableFrom(type)) {
                return f;
            }
        }

        throw new Exception("Filter not found");
    }

    private RememberMeServices getRememberMeServices() throws Exception {
        return getFilter(RememberMeAuthenticationFilter.class).getRememberMeServices();
    }

}
