

/*
 * 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.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.security.authentication.AuthenticationManager
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.BaseWebConfig;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.openid.OpenID4JavaConsumer
import org.springframework.security.openid.OpenIDAuthenticationFilter
import org.springframework.security.openid.OpenIDAuthenticationProvider
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource

/**
 * Tests to verify that all the functionality of <openid-login> attributes is present
 *
 * @author Rob Winch
 *
 */
public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
    def "http/openid-login"() {
        when:
            loadConfig(OpenIDLoginConfig)
        then:
            findFilter(OpenIDAuthenticationFilter).consumer.class == OpenID4JavaConsumer
        when:
            springSecurityFilterChain.doFilter(request,response,chain)
        then:
            response.getRedirectedUrl() == "http://localhost/login"
        when: "fail to log in"
            super.setup()
            request.servletPath = "/login/openid"
            request.method = "POST"
            springSecurityFilterChain.doFilter(request,response,chain)
        then: "sent to login error page"
            response.getRedirectedUrl() == "/login?error"
    }

    @Configuration
    static class OpenIDLoginConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) {
            http
                .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                .openidLogin()
                    .permitAll();
        }
    }

    def "http/openid-login/attribute-exchange"() {
        when:
            loadConfig(OpenIDLoginAttributeExchangeConfig)
            OpenID4JavaConsumer consumer = findFilter(OpenIDAuthenticationFilter).consumer
        then:
            consumer.class == OpenID4JavaConsumer

            def googleAttrs = consumer.attributesToFetchFactory.createAttributeList("https://www.google.com/1")
            googleAttrs[0].name == "email"
            googleAttrs[0].type == "http://axschema.org/contact/email"
            googleAttrs[0].required
            googleAttrs[1].name == "firstname"
            googleAttrs[1].type == "http://axschema.org/namePerson/first"
            googleAttrs[1].required
            googleAttrs[2].name == "lastname"
            googleAttrs[2].type == "http://axschema.org/namePerson/last"
            googleAttrs[2].required

            def yahooAttrs = consumer.attributesToFetchFactory.createAttributeList("https://rwinch.yahoo.com/rwinch/id")
            yahooAttrs[0].name == "email"
            yahooAttrs[0].type == "http://schema.openid.net/contact/email"
            yahooAttrs[0].required
            yahooAttrs[1].name == "fullname"
            yahooAttrs[1].type == "http://axschema.org/namePerson"
            yahooAttrs[1].required
        when:
            springSecurityFilterChain.doFilter(request,response,chain)
        then:
            response.getRedirectedUrl() == "http://localhost/login"
        when: "fail to log in"
            super.setup()
            request.servletPath = "/login/openid"
            request.method = "POST"
            springSecurityFilterChain.doFilter(request,response,chain)
        then: "sent to login error page"
            response.getRedirectedUrl() == "/login?error"
    }

    @Configuration
    static class OpenIDLoginAttributeExchangeConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) {
            http
                .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                .openidLogin()
                    .attributeExchange("https://www.google.com/.*") // attribute-exchange@identifier-match
                        .attribute("email") // openid-attribute@name
                            .type("http://axschema.org/contact/email") // openid-attribute@type
                            .required(true) // openid-attribute@required
                            .count(1) // openid-attribute@count
                            .and()
                        .attribute("firstname")
                            .type("http://axschema.org/namePerson/first")
                            .required(true)
                            .and()
                        .attribute("lastname")
                            .type("http://axschema.org/namePerson/last")
                            .required(true)
                            .and()
                        .and()
                    .attributeExchange(".*yahoo.com.*")
                        .attribute("email")
                            .type("http://schema.openid.net/contact/email")
                            .required(true)
                            .and()
                        .attribute("fullname")
                            .type("http://axschema.org/namePerson")
                            .required(true)
                            .and()
                        .and()
                    .permitAll();
        }
    }

    def "http/openid-login custom"() {
        setup:
            loadConfig(OpenIDLoginCustomConfig)
        when:
            springSecurityFilterChain.doFilter(request,response,chain)
        then:
            response.getRedirectedUrl() == "http://localhost/authentication/login"
        when: "fail to log in"
            super.setup()
            request.servletPath = "/authentication/login/process"
            request.method = "POST"
            springSecurityFilterChain.doFilter(request,response,chain)
        then: "sent to login error page"
            response.getRedirectedUrl() == "/authentication/login?failed"
    }

    @Configuration
    static class OpenIDLoginCustomConfig extends BaseWebConfig {
        protected void configure(HttpSecurity http) throws Exception {
            boolean alwaysUseDefaultSuccess = true;
            http
                .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                .openidLogin()
                    .permitAll()
                    .loginPage("/authentication/login") // openid-login@login-page
                    .failureUrl("/authentication/login?failed") // openid-login@authentication-failure-url
                    .loginProcessingUrl("/authentication/login/process") // openid-login@login-processing-url
                    .defaultSuccessUrl("/default", alwaysUseDefaultSuccess) // openid-login@default-target-url / openid-login@always-use-default-target
        }
    }

    def "http/openid-login custom refs"() {
        when:
            OpenIDLoginCustomRefsConfig.AUDS = Mock(AuthenticationUserDetailsService)
            loadConfig(OpenIDLoginCustomRefsConfig)
        then: "CustomWebAuthenticationDetailsSource is used"
            findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
            findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS
        when: "fail to log in"
            request.servletPath = "/login/openid"
            request.method = "POST"
            springSecurityFilterChain.doFilter(request,response,chain)
        then: "sent to login error page"
            response.getRedirectedUrl() == "/custom/failure"
    }

    @Configuration
    static class OpenIDLoginCustomRefsConfig extends BaseWebConfig {
        static AuthenticationUserDetailsService AUDS

        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .anyRequest().hasRole("USER")
                    .and()
                .openidLogin()
                    // if using UserDetailsService wrap with new UserDetailsByNameServiceWrapper<OpenIDAuthenticationToken>()
                    .authenticationUserDetailsService(AUDS) // openid-login@user-service-ref
                    .failureHandler(new SimpleUrlAuthenticationFailureHandler("/custom/failure")) // openid-login@authentication-failure-handler-ref
                    .successHandler(new SavedRequestAwareAuthenticationSuccessHandler( defaultTargetUrl : "/custom/targetUrl" )) // openid-login@authentication-success-handler-ref
                    .authenticationDetailsSource(new CustomWebAuthenticationDetailsSource()); // openid-login@authentication-details-source-ref
        }

        // only necessary to have easy access to the AuthenticationManager for testing/verification
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean()
                throws Exception {
            return super.authenticationManagerBean();
        }

    }

    static class CustomWebAuthenticationDetailsSource extends WebAuthenticationDetailsSource {}
}
