/*
 * Copyright (C) 2005 - 2011 Jaspersoft Corporation. All rights reserved.
 * http://www.jaspersoft.com.
 *
 * Unless you have purchased  a commercial license agreement from Jaspersoft,
 * the following license terms  apply:
 *
 * 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.jaspersoft.jasperserver.api.metadata.user.service.impl;

import com.jaspersoft.jasperserver.api.JSException;
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.providers.anonymous.AnonymousAuthenticationToken;
import org.springframework.security.userdetails.UserDetails;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import com.jaspersoft.jasperserver.api.metadata.user.domain.User;
import com.jaspersoft.jasperserver.api.metadata.user.domain.impl.client.MetadataUserDetails;

/**
 * To be used as part of an Acegi FilterChainProxy.
 * 
 * An authentication can exist that is not based on our internal security mechanism, such
 * as using an external LDAP service. This filter will assume that the external authentication
 * is what is wanted, and will:
 * 
 * <ul>
 *     <li>create a user in the metadata if it does not exist, adding any default internal roles</li>
 *     <li>synchronize the external roles with the user profile, adding and removing external roles</li>
 * </ul>
 *     
 * @author swood
 *
 */
public class MetadataAuthenticationProcessingFilter implements Filter, InitializingBean {

	private static Log log = LogFactory.getLog(MetadataAuthenticationProcessingFilter.class);

	protected ExternalUserService externalUserService;

    public void afterPropertiesSet() throws Exception {
        Assert.notNull(externalUserService);
    }

    /**
     * Does nothing - we reply on IoC lifecycle services instead.
     *
     * @param ignored not used
     *
     * @throws ServletException DOCUMENT ME!
     */
    public void init(FilterConfig ignored) throws ServletException {}

    /**
     * Does nothing - we reply on IoC lifecycle services instead.
     */
    public void destroy() {}

    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (log.isDebugEnabled()) {
        	if (auth == null) {
        		log.debug("No authentication token");
        	} else {
        		log.debug("Authentication token: '" + auth + "'");
        	}
        }
        // If we have authenticated, but not against an internal metadata store,
        // synch up with a possibly new user in our repository

        if (auth != null && auth.getPrincipal() != null &&
        		!(auth.getPrincipal() instanceof MetadataUserDetails) &&
                !(auth instanceof AnonymousAuthenticationToken)) {

            User principalUser = null;
            if (auth.getPrincipal() instanceof UserDetails) {
                UserDetails userDetails = (UserDetails) auth.getPrincipal();

                principalUser = getExternalUserService().maintainInternalUser(userDetails, auth.getAuthorities());
            } else if (auth.getPrincipal() instanceof String) {
                
                principalUser = getExternalUserService().maintainInternalUser((String) auth.getPrincipal(), auth.getAuthorities());
            } else {
                throw new JSException("Cannot synchronize user details. Unknown principal class: " + 
                           auth.getPrincipal().getClass().getName());
            }

            getExternalUserService().makeUserLoggedIn(principalUser);

            if (log.isDebugEnabled()) {
                log.debug(
                    "Populated SecurityContextHolder with JS authentication: '"
                    + SecurityContextHolder.getContext().getAuthentication()
                    + "'");
            }

            Authentication newAuth = SecurityContextHolder.getContext().getAuthentication();
            
            // The authentication can be null if there are no roles. This sets the anonymous user as the
            // logged-in user, if the anonymousUserFilter is in the chain
            
            if (newAuth != null && newAuth.getPrincipal() instanceof MetadataUserDetails) {
            	MetadataUserDetails newPrincipal = (MetadataUserDetails) newAuth.getPrincipal();

            	// Keep a hold of the original principal: it may be useful
            	// later
            	newPrincipal.setOriginalAuthentication(auth);
            }
        }
        
        chain.doFilter(request, response);

        if (log.isDebugEnabled()) {
            log.debug(
                "After chain, JI metadata token is: '"
                + SecurityContextHolder.getContext().getAuthentication()
                + "'");
        }
    }


	public ExternalUserService getExternalUserService() {
		return externalUserService;
	}

	public void setExternalUserService(ExternalUserService externalUserService) {
		this.externalUserService = externalUserService;
	}

}
