/**
 * @author tbellemb
 * @version 1.3
 * Creation date : 30.09.2004
 * Last modification date : 04/10/2005
 * Modifications :
 * 04/10/2005 - Thomas Bellembois - adding the cascading functionnality, adding the trusted users, adding the authentication router dependency configuration
 * 02/11/2005 - Thomas Bellembois - trustedPassword and trustedUsers parameters can be missing
 **/

package org.esupportail.filter.trustedFilter;

import java.io.IOException;
import java.util.Enumeration;

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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import fr.univrennes1.cri.util.commonUtils.CommonUtils;


public class TrustedFilter implements Filter{
	
	private String trustedPassword;	 
	private String trustedUsers;
	private boolean useAuthenticationRouter;
	
	private String filterName;
	
	private static final String SELECTED_FILTER = "org.esupportail.filter.authenticationRouter.selectedFilter";
	private static final String TRUSTED_PASSWORD = "org.esupportail.filter.trustedFilter.trustedPassword";
	private static final String TRUSTED_USERS = "org.esupportail.filter.trustedFilter.trustedUsers";
	private static final String USE_AUTHENTICATION_ROUTER = "org.esupportail.filter.trustedFilter.useAuthenticationRouter";
	private static final String AUTHENTICATION_TYPE = "basic";
	public final static String TRUSTED_FILTER_USER = "org.esupportail.filter.trustedFilter.user";
	public final static String FILTER_USED_FOR_THIS_SESSION = "org.esupportail.filter.trustedFilter.filterUsedForThisSession";	
	
	private final String AUTH_TYPE = "Basic";
	private final String AUTH_REALM = "ESUP WebDAV Server";
	
	private final String MICROSOFT_MINIREDIR = "microsoft-webdav-miniredir";
	
	//	 logger
	private static final Log log = LogFactory.getLog(TrustedFilter.class);
	
	
	public void init(FilterConfig config) throws ServletException {
		
		Enumeration parameterNames = config.getInitParameterNames();
		boolean trustedPasswordParamPresent = false; 
		boolean trustedUsersParamPresent = false;
		while (parameterNames.hasMoreElements()) {
			String parameterName = (String) parameterNames.nextElement();
			if (parameterName.equalsIgnoreCase(TRUSTED_USERS)) trustedUsersParamPresent = true;
			if (parameterName.equalsIgnoreCase(TRUSTED_PASSWORD)) trustedPasswordParamPresent = true;
		}// while (parameterNames.hasMoreElements())
		
		if (trustedPasswordParamPresent) trustedPassword = config.getInitParameter(TRUSTED_PASSWORD);
		else trustedPassword = "";
		if (trustedUsersParamPresent) trustedUsers = config.getInitParameter(TRUSTED_USERS);
		else trustedUsers = "";
		filterName = config.getFilterName().toUpperCase();
		useAuthenticationRouter = Boolean.valueOf(config.getInitParameter(USE_AUTHENTICATION_ROUTER)).booleanValue();
	
		log.debug("trustedPassword=**hidden**\n");
		log.debug("trustedUsers="+trustedUsers+"\n");
		log.debug("filterName="+filterName+"\n");
		log.debug("useAuthenticationRouter="+useAuthenticationRouter+"\n");
	}// init
	
		
	/*
	 * Make the String lenght multiple of for FOR A BASE64 ENCODING or DECODING
	 */
	private static String makeMultipleOf4(String s){
		StringBuffer result = new StringBuffer(s);
		int stringLength = s.length();
		if(stringLength%4!=0){
			for(int i=0; i<(4-(stringLength%4)); i++){
				result.append("=");
			}// for
		}// if
		return result.toString();
	}// makeMultipleOf4
	
	
	
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
		
		
		log.info("Entering TrustedFilter "+filterName);
		
		
		//////////////////////////////////////
		// make sure we've got an HTTP request
		//
		if (!(request instanceof HttpServletRequest)
				|| !(response instanceof HttpServletResponse))
			throw new ServletException(getClass().getName()+".doFilter() - TrustedFilter protects only HTTP resources");
		
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////////
		// checking that the selectedFilter session variable is set to TRUSTED (by the AuthenticationRouter filter)
		//
		//
		HttpSession session = ((HttpServletRequest) request).getSession();
		
//		if (session == null || session.getAttribute(SELECTED_FILTER)==null || !session.getAttribute(SELECTED_FILTER).equals("TRUSTED")){
//			log.debug("No session or session attribut SELECTED_FILTER not set to TRUSTED");
//			log.info("Leaving TrustedFilter...");
//			filterChain.doFilter(request, response);
//			return;
//		}// if
//		else{
//		log.debug("Session attribut SELECTED_FILTER set to TRUSTED");
//		}// else
		
		// Cascading modifications
		if (useAuthenticationRouter) {
			log.debug("Use AuthenticationRouter");
			if (session == null || session.getAttribute(SELECTED_FILTER)==null){
				log.debug("No session attribute");
				log.info("Leaving TrustedFilter "+filterName);
				filterChain.doFilter(request, response);
				return;
			}// if (session == null || session.getAttribute(SELECTED_FILTER)==null)
			else if (session.getAttribute(SELECTED_FILTER)!=null) {
				String sessionAttributes = (String)session.getAttribute(SELECTED_FILTER);
				sessionAttributes = sessionAttributes.toUpperCase();
				log.debug("sessionAttributes="+sessionAttributes);
				if (sessionAttributes.indexOf(filterName) == -1) {
					log.debug("Session attribute SELECTED_FILTER not set to "+filterName);
					log.info("Leaving TrustedFilter "+filterName);
					filterChain.doFilter(request, response);
					return;
				}// if (sessionAttributes.indexOf("TRUSTED") == -1)
				else{
					log.debug("Session attribute SELECTED_FILTER set to "+filterName);
				}// else
			}// else if (session.getAttribute(SELECTED_FILTER)!=null)
		}// if (useAuthenticationRouter)
		else {
			log.debug("Do not use AuthenticationRouter");
		}// else (useAuthenticationRouter)
		// !Cascading modifications
		
		///////////////////////
		// wrapping the request
		//
		request = new TrustedFilterRequestWrapper((HttpServletRequest)request);
		
		
		////////////////////////////////////////////////////
		// if the user is already authenticated - do nothing
		//
//		if(session.getAttribute(TRUSTED_FILTER_USER)!=null){
//			log.debug("Session in progress");
//			log.info("Leaving TrustedFilter "+filterName);
//			filterChain.doFilter(request, response);
//			return;
//		}//if
		if(session.getAttribute(FILTER_USED_FOR_THIS_SESSION)!=null && session.getAttribute(FILTER_USED_FOR_THIS_SESSION).equals(filterName)){
			log.debug("Session in progress");
			log.info("Leaving TrustedFilter "+filterName);
			filterChain.doFilter(request, response);
			return;
		}//if
		
		
		/////////////////////////////////////
		// getting the header "authorization"
		//
		String header = ((HttpServletRequest)request).getHeader("authorization");
		
		
		/////////////////////////////////////////////////////////////////////////////////////////
		// if there is no "authorization" header then send a 401 error and ask for authentication
		//
		if(header==null || header.equals("")){
			log.debug("No authorization header");
			log.info("Leaving TrustedFilter "+filterName);
			((HttpServletResponse)response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
			((HttpServletResponse)response).setHeader("WWW-Authenticate", AUTH_TYPE+" realm=\""+AUTH_REALM+"\"");
			return;
		}// if
		
		
		////////////////////////////////////////////////////////////////////////////////
		// processing the header to get authentication informations (login and password)
		//
		// removing the authentication type (and spaces) from the header
		String encodedAuthorization = header.substring(AUTHENTICATION_TYPE.length()).trim();
		// decoding the authorization
		String decodedAuthorization = Base64CoDec.decode(makeMultipleOf4(encodedAuthorization));
		// getting the password
		String password = decodedAuthorization.substring(decodedAuthorization.indexOf(":")+1);
		// getting the user (to save in session variable)
		String user = decodedAuthorization.substring(0, decodedAuthorization.indexOf(":"));
		
		
		//////////////////////////////////////////////////////////////////////////////////////////////
		// when the agent is MICROSOFT_MINIREDIR then the user is host\\user, we need to remove host\\
		//
		String request_agent = (((HttpServletRequest)request).getHeader("user-agent")).toLowerCase(); 
		if (StringUtils.contains(request_agent, MICROSOFT_MINIREDIR)) {
			log.debug("agent="+MICROSOFT_MINIREDIR);
			log.debug("user="+user+" rebuilding string...");
			if (StringUtils.contains(user, "\\")) user = StringUtils.substringAfterLast(user, "\\");
		}// if (StringUtils.contains(request_agent, MICROSOFT_MINIREDIR)) 
		// ESUP
		
		log.debug("user > "+user);
		
		
		////////////////////////////////////////////////////////////////////////////////////////////////////////
		// if the password is not the trustedPassword and the connected user not a trusted user send a 401 error
		// and ask for authentication else continue
		//
		boolean authenticationVerified = true;
		if(!trustedPassword.equalsIgnoreCase("") && !password.equals(trustedPassword)){
			// password not trusted - we leave
			log.info("Password not verified");
			log.info("Leaving TrustedFilter "+filterName);
			authenticationVerified = false;
			((HttpServletResponse)response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
			((HttpServletResponse)response).setHeader("WWW-Authenticate", AUTH_TYPE+" realm=\""+AUTH_REALM+"\"");        	
			return;
		}//if
		if (!trustedUsers.equalsIgnoreCase("")) {
			boolean allowedUserFound = false;
			Enumeration allowedUsers = CommonUtils.convertStringToEnumeration(trustedUsers, ":");
			while (allowedUsers.hasMoreElements() && !allowedUserFound) {
				String allowedUser = (String) allowedUsers.nextElement();
				if (allowedUser.equalsIgnoreCase(user)) allowedUserFound = true;
			}// while (allowedUsers.hasMoreElements() && !allowedUserFound)
			if (!allowedUserFound) {
				// users not trusted - we leave
				log.info("User not verified");
				log.info("Leaving TrustedFilter "+filterName);
				authenticationVerified = false;
				((HttpServletResponse)response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
				((HttpServletResponse)response).setHeader("WWW-Authenticate", AUTH_TYPE+" realm=\""+AUTH_REALM+"\"");         	
				return;
			}// if (!allowedUserFound)
		}// if (!trustedUsers.equalsIgnoreCase(""))
		if (authenticationVerified) {
			// authentication verified - have fun !
			log.info("Authentication verified");
			// store the authenticated user in the session
			if (session != null){
				// probably unncessary
				session.setAttribute(TRUSTED_FILTER_USER, user);
				session.setAttribute(FILTER_USED_FOR_THIS_SESSION, filterName);
			}// if
			log.info("Leaving TrustedFilter "+filterName);
			filterChain.doFilter(request, response);
			return;
		}// if (authenticationVerified) 
	}// doFilter
	
	public void destroy() {
	}// destroy
	
}// TrustedFilter
