/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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
 *
 *    https://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 grails.plugin.springsecurity.cas

import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic

import org.apereo.cas.client.proxy.Cas20ProxyRetriever
import org.apereo.cas.client.proxy.ProxyGrantingTicketStorageImpl
import org.apereo.cas.client.session.SingleSignOutFilter
import org.apereo.cas.client.session.SingleSignOutHttpSessionListener
import org.apereo.cas.client.validation.Cas20ServiceTicketValidator

import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean
import org.springframework.core.Ordered
import org.springframework.security.cas.ServiceProperties
import org.springframework.security.cas.authentication.CasAuthenticationProvider
import org.springframework.security.cas.authentication.NullStatelessTicketCache
import org.springframework.security.cas.web.CasAuthenticationEntryPoint
import org.springframework.security.cas.web.CasAuthenticationFilter

import grails.plugin.springsecurity.SecurityFilterPosition
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugins.Plugin

@CompileStatic
class SpringSecurityCasGrailsPlugin extends Plugin {

    String grailsVersion = '7.0.0 > *'
    String author = 'Burt Beckwith'
    String authorEmail = ''
    String title = 'Jasig CAS support for the Spring Security plugin.'
    String description = 'Jasig CAS support for the Spring Security plugin.'
    String documentation = 'https://apache.github.io/grails-spring-security'
    String license = 'APACHE'
    List loadAfter = ['springSecurityCore']
    def organization = [name: 'Grails', url: 'https://www.grails.org']
    def issueManagement = [url: 'https://github.com/apache/grails-spring-security/issues']
    def scm = [url: 'https://github.com/apache/grails-spring-security']
    def profiles = ['web']

    @CompileDynamic
    Closure doWithSpring() {
        { ->

            def conf = SpringSecurityUtils.securityConfig
            if (!conf || !conf.active) {
                return
            }

            SpringSecurityUtils.loadSecondaryConfig 'DefaultCasSecurityConfig'
            // have to get again after overlaying DefaultCasSecurityConfig
            conf = SpringSecurityUtils.securityConfig

            if (!conf.cas.active) {
                return
            }

            boolean printStatusMessages = (conf.printStatusMessages instanceof Boolean) ? conf.printStatusMessages : true

            if (printStatusMessages) {
                println '\nConfiguring Spring Security CAS ...'
            }

            ['serverUrlPrefix', 'loginUri', 'serviceUrl'].each { requiredConfigKey ->
                if (!conf.cas[requiredConfigKey]) {
                    throw new IllegalStateException("cas.${requiredConfigKey} is not set: this is a required configuration value when cas.active is true")
                }
            }

            if (conf.cas.useSingleSignout) {

                // session fixation prevention breaks single signout because
                // the service ticket is mapped to the session id which changes
                conf.useSessionFixationPrevention = false

                singleSignOutFilter(SingleSignOutFilter) {
                    ignoreInitConfiguration = true
                }

                singleSignOutFilterRegistrationBean(FilterRegistrationBean) {
                    name = 'CAS Single Sign Out Filter'
                    filter = ref('singleSignOutFilter')
                    order = Ordered.HIGHEST_PRECEDENCE
                }

                singleSignOutHttpSessionListener(ServletListenerRegistrationBean, new SingleSignOutHttpSessionListener())
            }

            SpringSecurityUtils.registerProvider 'casAuthenticationProvider'
            SpringSecurityUtils.registerFilter 'casAuthenticationFilter', SecurityFilterPosition.CAS_FILTER

            // TODO  document NullProxyGrantingTicketStorage
            casProxyGrantingTicketStorage(ProxyGrantingTicketStorageImpl)

            authenticationEntryPoint(CasAuthenticationEntryPoint) {
                serviceProperties = ref('casServiceProperties')
                loginUrl = conf.cas.serverUrlPrefix + conf.cas.loginUri
            }

            casServiceProperties(ServiceProperties) {
                service = conf.cas.serviceUrl
                sendRenew = conf.cas.sendRenew // false
                artifactParameter = conf.cas.artifactParameter // 'ticket'
                serviceParameter = conf.cas.serviceParameter // 'service'
            }

            casAuthenticationFilter(CasAuthenticationFilter) {
                authenticationManager = ref('authenticationManager')
                sessionAuthenticationStrategy = ref('sessionAuthenticationStrategy')
                authenticationSuccessHandler = ref('authenticationSuccessHandler')
                authenticationFailureHandler = ref('authenticationFailureHandler')
                rememberMeServices = ref('rememberMeServices')
                authenticationDetailsSource = ref('authenticationDetailsSource')
                serviceProperties = ref('casServiceProperties')
                proxyGrantingTicketStorage = ref('casProxyGrantingTicketStorage')
                filterProcessesUrl = conf.cas.filterProcessesUrl // '/login/cas'
                continueChainBeforeSuccessfulAuthentication = conf.apf.continueChainBeforeSuccessfulAuthentication
                // false
                allowSessionCreation = conf.apf.allowSessionCreation // true
                proxyReceptorUrl = conf.cas.proxyReceptorUrl
            }

            casProxyRetriever(Cas20ProxyRetriever, conf.cas.serverUrlPrefix, conf.cas.serverUrlEncoding /*'UTF-8'*/)

            casTicketValidator(Cas20ServiceTicketValidator, conf.cas.serverUrlPrefix) {
                proxyRetriever = ref('casProxyRetriever')
                proxyGrantingTicketStorage = ref('casProxyGrantingTicketStorage')
                proxyCallbackUrl = conf.cas.proxyCallbackUrl
                renew = conf.cas.sendRenew // false
            }

            casStatelessTicketCache(NullStatelessTicketCache)

            casAuthenticationProvider(CasAuthenticationProvider) {
                authenticationUserDetailsService = ref('authenticationUserDetailsService')
                serviceProperties = ref('casServiceProperties')
                ticketValidator = ref('casTicketValidator')
                statelessTicketCache = ref('casStatelessTicketCache')
                key = conf.cas.key // 'grails-spring-security-cas'
            }

            if (printStatusMessages) {
                println '... finished configuring Spring Security CAS\n'
            }
        }
    }
}
