/*
 * 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 org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import javax.servlet.http.Cookie

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration
import org.springframework.mock.web.MockFilterChain
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.mock.web.MockHttpSession
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.authentication.AuthenticationSuccessHandler
import org.springframework.security.web.authentication.RememberMeServices
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter
import org.springframework.security.web.context.HttpRequestResponseHolder
import org.springframework.security.web.context.HttpSessionSecurityContextRepository
import org.springframework.test.util.ReflectionTestUtils;

/**
 * Tests to verify that all the functionality of <anonymous> attributes is present
 *
 * @author Rob Winch
 *
 */
public class NamespaceRememberMeTests extends BaseSpringSpec {

    def "http/remember-me"() {
        setup:
            loadConfig(RememberMeConfig)
        when: "login with remember me"
            super.setup()
            request.servletPath = "/login"
            request.method = "POST"
            request.parameters.username = ["user"] as String[]
            request.parameters.password = ["password"] as String[]
            request.parameters.'remember-me' = ["true"] as String[]
            springSecurityFilterChain.doFilter(request,response,chain)
            Cookie rememberMeCookie = getRememberMeCookie()
        then: "response contains remember me cookie"
            rememberMeCookie != null
        when: "session expires"
            super.setup()
            request.setCookies(rememberMeCookie)
            request.requestURI = "/abc"
            springSecurityFilterChain.doFilter(request,response,chain)
            MockHttpSession session = request.getSession()
        then: "initialized to RememberMeAuthenticationToken"
            SecurityContext context = new HttpSessionSecurityContextRepository().loadContext(new HttpRequestResponseHolder(request, response))
            context.getAuthentication() instanceof RememberMeAuthenticationToken
        when: "logout"
            super.setup()
            request.setSession(session)
            super.setupCsrf()
            request.setCookies(rememberMeCookie)
            request.servletPath = "/logout"
            request.method = "POST"
            springSecurityFilterChain.doFilter(request,response,chain)
            rememberMeCookie = getRememberMeCookie()
        then: "logout cookie expired"
            response.getRedirectedUrl() == "/login?logout"
            rememberMeCookie.maxAge == 0
        when: "use remember me after logout"
            super.setup()
            request.setCookies(rememberMeCookie)
            request.requestURI = "/abc"
            springSecurityFilterChain.doFilter(request,response,chain)
        then: "sent to default login page"
            response.getRedirectedUrl() == "http://localhost/login"
    }

    @Configuration
    static class RememberMeConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                .formLogin()
                    .and()
                .rememberMe()
        }
    }

    def "http/remember-me@services-ref"() {
        setup:
            RememberMeServicesRefConfig.REMEMBER_ME_SERVICES = Mock(RememberMeServices)
        when: "use custom remember-me services"
            loadConfig(RememberMeServicesRefConfig)
        then: "custom remember-me services used"
            findFilter(RememberMeAuthenticationFilter).rememberMeServices == RememberMeServicesRefConfig.REMEMBER_ME_SERVICES
            findFilter(UsernamePasswordAuthenticationFilter).rememberMeServices == RememberMeServicesRefConfig.REMEMBER_ME_SERVICES
    }

    @Configuration
    static class RememberMeServicesRefConfig extends BaseWebConfig {
        static RememberMeServices REMEMBER_ME_SERVICES
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .rememberMeServices(REMEMBER_ME_SERVICES)
        }
    }

    def "http/remember-me@authentication-success-handler-ref"() {
        setup:
            AuthSuccessConfig.SUCCESS_HANDLER = Mock(AuthenticationSuccessHandler)
        when: "use custom success handler"
            loadConfig(AuthSuccessConfig)
        then: "custom remember-me success handler is used"
            findFilter(RememberMeAuthenticationFilter).successHandler == AuthSuccessConfig.SUCCESS_HANDLER
    }

    @Configuration
    static class AuthSuccessConfig extends BaseWebConfig {
        static AuthenticationSuccessHandler SUCCESS_HANDLER
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .authenticationSuccessHandler(SUCCESS_HANDLER)
        }
    }

    // http/remember-me@data-source-ref is not supported directly. Instead use http/remember-me@token-repository-ref example

    def "http/remember-me@key"() {
        when: "use custom key"
            loadConfig(KeyConfig)
            AuthenticationManager authManager = context.getBean(AuthenticationManager)
        then: "custom key services used"
            findFilter(RememberMeAuthenticationFilter).rememberMeServices.key == "KeyConfig"
            authManager.authenticate(new RememberMeAuthenticationToken("KeyConfig", "user", AuthorityUtils.createAuthorityList("ROLE_USER")))
    }

    @Configuration
    static class KeyConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .key("KeyConfig")
        }

        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean()
                throws Exception {
            return super.authenticationManagerBean();
        }
    }

    // http/remember-me@services-alias is not supported use standard aliasing instead (i.e. @Bean("alias"))

    def "http/remember-me@token-repository-ref"() {
        setup:
            TokenRepositoryRefConfig.TOKEN_REPOSITORY = Mock(PersistentTokenRepository)
        when: "use custom token services"
            loadConfig(TokenRepositoryRefConfig)
        then: "custom token services used with PersistentTokenBasedRememberMeServices"
            PersistentTokenBasedRememberMeServices rememberMeServices = findFilter(RememberMeAuthenticationFilter).rememberMeServices
            findFilter(RememberMeAuthenticationFilter).rememberMeServices.tokenRepository == TokenRepositoryRefConfig.TOKEN_REPOSITORY
    }

    @Configuration
    static class TokenRepositoryRefConfig extends BaseWebConfig {
        static PersistentTokenRepository TOKEN_REPOSITORY
        protected void configure(HttpSecurity http) throws Exception {
            // JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl()
            // tokenRepository.setDataSource(dataSource);
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .tokenRepository(TOKEN_REPOSITORY)
        }
    }

    def "http/remember-me@token-validity-seconds"() {
        when: "use token validity"
            loadConfig(TokenValiditySecondsConfig)
        then: "custom token validity used"
            findFilter(RememberMeAuthenticationFilter).rememberMeServices.tokenValiditySeconds == 1
    }

    @Configuration
    static class TokenValiditySecondsConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .tokenValiditySeconds(1)
        }
    }

    def "http/remember-me@token-validity-seconds default"() {
        when: "use token validity"
            loadConfig(DefaultTokenValiditySecondsConfig)
        then: "custom token validity used"
            findFilter(RememberMeAuthenticationFilter).rememberMeServices.tokenValiditySeconds == AbstractRememberMeServices.TWO_WEEKS_S
    }

    @Configuration
    static class DefaultTokenValiditySecondsConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
        }
    }

    def "http/remember-me@use-secure-cookie"() {
        when: "use secure cookies = true"
            loadConfig(UseSecureCookieConfig)
        then: "secure cookies will be used"
            ReflectionTestUtils.getField(findFilter(RememberMeAuthenticationFilter).rememberMeServices, "useSecureCookie") == true
    }

    @Configuration
    static class UseSecureCookieConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .useSecureCookie(true)
        }
    }

    def "http/remember-me@use-secure-cookie defaults"() {
        when: "use secure cookies not specified"
            loadConfig(DefaultUseSecureCookieConfig)
        then: "secure cookies will be null (use secure if the request is secure)"
            ReflectionTestUtils.getField(findFilter(RememberMeAuthenticationFilter).rememberMeServices, "useSecureCookie") == null
    }

    @Configuration
    static class DefaultUseSecureCookieConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
        }
    }

    def "http/remember-me defaults UserDetailsService with custom UserDetailsService"() {
        setup:
            DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE = Mock(UserDetailsService)
            loadConfig(DefaultsUserDetailsServiceWithDaoConfig)
        when:
            request.setCookies(createRememberMeCookie())
            springSecurityFilterChain.doFilter(request, response, chain)
        then: "RememberMeServices defaults to the custom UserDetailsService"
           1 * DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE.loadUserByUsername("user")
    }

    @Configuration
    @EnableWebSecurity
    static class DefaultsUserDetailsServiceWithDaoConfig extends WebSecurityConfigurerAdapter {
        static UserDetailsService USERDETAILS_SERVICE

        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
        }

        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth
                    .userDetailsService(USERDETAILS_SERVICE);
        }
    }

    def "http/remember-me@user-service-ref"() {
        setup:
            UserServiceRefConfig.USERDETAILS_SERVICE = Mock(UserDetailsService)
        when: "use custom UserDetailsService"
            loadConfig(UserServiceRefConfig)
        then: "custom UserDetailsService is used"
            ReflectionTestUtils.getField(findFilter(RememberMeAuthenticationFilter).rememberMeServices, "userDetailsService") == UserServiceRefConfig.USERDETAILS_SERVICE
    }

    @Configuration
    static class UserServiceRefConfig extends BaseWebConfig {
        static UserDetailsService USERDETAILS_SERVICE
        protected void configure(HttpSecurity http) throws Exception {
            http
                .formLogin()
                    .and()
                .rememberMe()
                    .userDetailsService(USERDETAILS_SERVICE)
        }
    }

    Cookie createRememberMeCookie() {
        MockHttpServletRequest request = new MockHttpServletRequest()
        MockHttpServletResponse response = new MockHttpServletResponse()
        super.setupCsrf("CSRF_TOKEN", request, response)

        MockFilterChain chain = new MockFilterChain()
        request.servletPath = "/login"
        request.method = "POST"
        request.parameters.username = ["user"] as String[]
        request.parameters.password = ["password"] as String[]
        request.parameters.'remember-me' = ["true"] as String[]
        springSecurityFilterChain.doFilter(request, response, chain)
        response.getCookie("remember-me")
    }

    Cookie getRememberMeCookie(String cookieName="remember-me") {
        response.getCookie(cookieName)
    }
}
