/*
 * Copyright 2002-2014 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 sample.ui.config;

import java.util.Collections;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
 * Class containing all security methods and beans.
 *
 * @author Arnaldo Piccinelli
 */
@Configuration
@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MultiHttpSecurityConfig {

	private static final String[] UNSECURED_RESOURCE_LIST = new String[] { "/resources/**", "/assets/**", "/css/**",
			"/webjars/**", "/images/**", "/dandelion/**", "/js/**" };

	private static final String[] UNAUTHORIZED_RESOURCE_LIST = new String[] { "/test.html", "/", "/unauthorized*",
			"/error*", "/users*", "/accessDenied" };

	@Configuration
	@Profile({ "dev" })
	protected static class InMemoryPersistentTokenRememberMeSetup {
		@Value("${rememberMeToken}")
		private String rememberMeToken;

		@Value("${rememberMeParameter}")
		private String rememberMeParameter;

		@Bean
		public RememberMeServices getRememberMeServices() {
			PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(
					rememberMeToken, new BasicRememberMeUserDetailsService(), new InMemoryTokenRepositoryImpl());
			services.setParameter(rememberMeParameter);
			return services;
		}

		public class BasicRememberMeUserDetailsService implements UserDetailsService {
			@Override
			public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
				return new User(username, "", Collections.<GrantedAuthority> emptyList());
			}
		}
	}

	@Configuration
	@Profile({ "test", "live" })
	protected static class JdbcPersistentTokenRememberMeSetup {
		@Value("${rememberMeToken}")
		private String rememberMeToken;

		@Value("${rememberMeParameter}")
		private String rememberMeParameter;

		@Autowired
		private DataSource dataSource;

		@Bean
		public RememberMeServices getRememberMeServices() {
			JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager();
			jdbcUserDetailsManager.setDataSource(dataSource);

			JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
			jdbcTokenRepositoryImpl.setDataSource(dataSource);

			PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices(
					rememberMeToken, jdbcUserDetailsManager, jdbcTokenRepositoryImpl);
			services.setParameter(rememberMeParameter);
			return services;
		}
	}

	@Order(Ordered.HIGHEST_PRECEDENCE)
	@Configuration
	protected static class ExternalAuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter {
		@Autowired
		private DataSource dataSource;

		@Override
		public void init(AuthenticationManagerBuilder auth) throws Exception {
			//@formatter:off
			String authoritiesByUsernameQuery = "select username, authority from user_authorities " +
					"inner join users on user_authorities.user_id = users.id " +
					"inner join authorities on user_authorities.authority_id = authorities.id " +
					"where username = ?";

			JdbcUserDetailsManager userDetailsService = new JdbcUserDetailsManager();
			userDetailsService.setDataSource(dataSource);
			userDetailsService.setAuthoritiesByUsernameQuery(authoritiesByUsernameQuery);
			PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

			auth
				.userDetailsService(userDetailsService)
					.passwordEncoder(passwordEncoder)
				.and()
					.jdbcAuthentication()
						.authoritiesByUsernameQuery(authoritiesByUsernameQuery)
						.passwordEncoder(passwordEncoder)
						.dataSource(dataSource)
			;
			//@formatter:on
		}
	}

	@Configuration
	@Order(1)
	@Profile({ "live" })
	public static class LiveWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
		@Value("${rememberMeToken}")
		private String rememberMeToken;

		@Value("${spring.profiles.active}")
		private String activeProfile;

		@Autowired
		RememberMeServices rememberMeServices;

		@Override
		public void configure(WebSecurity web) throws Exception {
			//@formatter:off
			web
				.ignoring()
					.antMatchers(UNSECURED_RESOURCE_LIST);
			//@formatter:on
		}

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			//@formatter:off
			http
				.headers()
					.frameOptions()
						.sameOrigin()
			.and()
				.authorizeRequests()
					.antMatchers(UNAUTHORIZED_RESOURCE_LIST)
						.permitAll()
					.antMatchers("/git", "/manage", "/manage/**")
						.hasRole("ADMIN")
					.anyRequest()
						.authenticated()
			.and()
				.formLogin()
					.loginPage("/login")
					.permitAll()
			.and()
				.headers()
					.cacheControl()
				.and()
					.frameOptions()
						.deny()
			.and()
				.exceptionHandling()
					.accessDeniedPage("/access?error")
			.and()
				.rememberMe()
					.useSecureCookie(true)
					.tokenValiditySeconds(60 * 60 * 24 * 10) // 10 days
					.rememberMeServices(rememberMeServices)
					.key(rememberMeToken)
			.and()
				.logout()
					.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
					.logoutSuccessUrl("/?logout")
			.and()
				.sessionManagement()
					.maximumSessions(1)
					.expiredUrl("/login?expired");
			// @formatter:on
		}
	}

	@Configuration
	@Order(1)
	@Profile({ "dev", "test" })
	public static class NonLiveWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
		@Value("${rememberMeToken}")
		private String rememberMeToken;

		@Value("${spring.profiles.active}")
		private String activeProfile;

		@Autowired
		RememberMeServices rememberMeServices;

		@Override
		public void configure(WebSecurity web) throws Exception {
			//@formatter:off
			web
				.ignoring()
					.antMatchers(UNSECURED_RESOURCE_LIST);
			//@formatter:on
		}

		@Override
		protected void configure(HttpSecurity http) throws Exception {
			//@formatter:off
			http
				.headers()
					.frameOptions()
						.sameOrigin()
			.and()
				.authorizeRequests()
					.antMatchers(UNAUTHORIZED_RESOURCE_LIST)
						.permitAll()
					.antMatchers("/git", "/manage", "/manage/**")
						.permitAll()
					.anyRequest()
						.authenticated()
			.and()
				.formLogin()
					.loginPage("/login")
					.permitAll()
			.and()
				.headers()
					.cacheControl()
				.and()
					.frameOptions()
						.deny()
			.and()
				.exceptionHandling()
					.accessDeniedPage("/access?error")
			.and()
				.rememberMe()
					.useSecureCookie(true)
					.tokenValiditySeconds(60 * 60 * 24 * 10) // 10 days
					.rememberMeServices(rememberMeServices)
					.key(rememberMeToken)
			.and()
				.logout()
					.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
					.logoutSuccessUrl("/?logout")
			.and()
				.sessionManagement()
					.maximumSessions(1)
					.expiredUrl("/login?expired");
			// @formatter:on
		}
	}

	// Register HttpSessionEventPublisher
	@Bean
	public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
		return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
	}
}