/*
 * Copyright (C) 2008-2020, Juick
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.juick.config;

import com.juick.SignatureManager;
import com.juick.service.UserService;
import com.juick.service.security.HTTPSignatureAuthenticationFilter;
import com.juick.service.security.HashParamAuthenticationFilter;
import com.juick.service.security.JuickUserDetailsService;
import com.juick.service.security.entities.JuickUser;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.NullRememberMeServices;
import org.springframework.security.web.authentication.RememberMeServices;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.annotation.Resource;
import javax.inject.Inject;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * Created by aalexeev on 11/21/16.
 */
@EnableWebSecurity
public class SecurityConfig {
    @Resource
    private UserService userService;

    private static final String COOKIE_NAME = "juick-remember-me";

    @Bean
    public UserDetailsService userDetailsService() {
        return new JuickUserDetailsService(userService);
    }
    @Bean
    static CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        source.registerCorsConfiguration("/u/**", configuration);
        source.registerCorsConfiguration("/n/**", configuration);
        return source;
    }

    @Configuration
    @Order(1)
    public static class ApiConfig extends WebSecurityConfigurerAdapter {
        @Value("${auth_remember_me_key:secret}")
        private String rememberMeKey;
        @Resource
        private UserService userService;
        @Resource
        private SignatureManager signatureManager;
        ApiConfig() {
            super(true);
        }
        @Bean
        RememberMeServices apiTokenServices() {
            return new NullRememberMeServices();
        }
        @Bean
        public HashParamAuthenticationFilter apiAuthenticationFilter() {
            return new HashParamAuthenticationFilter(userService, apiTokenServices());
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/api/**")
                    .addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class)
                    .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class)
                    .authorizeRequests()
                    .antMatchers(HttpMethod.OPTIONS).permitAll()
                    .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions",
                            "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk",
                            "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin",
                            "/api/_google", "/api/_applelogin", "/api/signup", "/api/inbox", "/api/events", "/api/info/**",
                            "/api/nodeinfo/2.0").permitAll()
                    .anyRequest().hasRole("USER")
                    .and()
                    .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY)
                    .and()
                    .httpBasic().authenticationEntryPoint(juickAuthenticationEntryPoint())
                    .and().cors().configurationSource(corsConfigurationSource())
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().exceptionHandling().authenticationEntryPoint(juickAuthenticationEntryPoint())
                    .and()
                    .rememberMe()
                    .alwaysRemember(true)
                    .tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(6 * 30))
                    .rememberMeServices(apiTokenServices())
                    .key(rememberMeKey)
                    .and()
                    .headers().defaultsDisabled().cacheControl();
        }

        @Bean
        public AuthenticationEntryPoint juickAuthenticationEntryPoint() {
            var entryPoint = new BasicAuthenticationEntryPoint();
            entryPoint.setRealmName("Juick");
            return entryPoint;
        }
    }

    @Configuration
    public static class WebConfig extends WebSecurityConfigurerAdapter {
        @Value("${auth_remember_me_key:secret}")
        private String rememberMeKey;
        @Value("${web_domain:localhost}")
        private String webDomain;
        @Resource
        private UserService userService;
        @Inject
        private UserDetailsService userDetailsService;
        @Bean
        @Qualifier("www")
        public HashParamAuthenticationFilter wwwAuthenticationFilter() {
            return new HashParamAuthenticationFilter(userService, hashCookieServices());
        }
        @Bean
        @Qualifier("www")
        public RememberMeServices hashCookieServices() {
            TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(
                    rememberMeKey, userDetailsService);

            services.setCookieName(COOKIE_NAME);
            services.setCookieDomain(webDomain);
            services.setAlwaysRemember(true);
            services.setTokenValiditySeconds(6 * 30 * 24 * 3600);
            services.setUseSecureCookie(false); // TODO set true if https is supports

            return services;
        }
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .addFilterBefore(wwwAuthenticationFilter(), BasicAuthenticationFilter.class)
                    .authorizeRequests()
                    .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/post2", "/comment")
                    .authenticated()
                    .antMatchers("/actuator/**").hasRole("ADMIN")
                    .anyRequest().permitAll()
                    .and()
                    .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY)
                    .and().cors().configurationSource(corsConfigurationSource())
                    .and().sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .invalidSessionUrl("/")
                    .and()
                    .logout()
                    .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
                    .invalidateHttpSession(true)
                    .logoutUrl("/logout")
                    .logoutSuccessUrl("/")
                    .deleteCookies("hash", COOKIE_NAME)
                    .and()
                    .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .defaultSuccessUrl("/")
                    .loginProcessingUrl("/login")
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .failureUrl("/login?error=1")
                    .and()
                    .rememberMe()
                    .rememberMeCookieDomain(webDomain).key(rememberMeKey)
                    .rememberMeServices(hashCookieServices())
                    .and()
                    .csrf().disable()
                    .headers().defaultsDisabled().cacheControl();
        }
    }
}