/**
 * @author tbellemb
 * @version 1.4
 * Creation date : 8.10.2004
 * Last modification date : 2005 10 04
 * Modifications :
 * 2005 05 04 - Thomas Bellembois - fixed a bug with the useSecureRequest parameter
 * 2005 06 06 - Thomas Bellembois - fixed a big with the request_agent parameter
 * 2005 10 04 - Thomas Bellembois - adding the cascading functionnality
 */
package org.esupportail.filter.authenticationRouter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.StringTokenizer;
import java.util.Vector;

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.logging.Log;
import org.apache.commons.logging.LogFactory;


public class AuthenticationRouter implements Filter {
	
	
	/*
	 * 
	 * @author tbellemb
	 * Container used to store filter parameters
	 */
	private class FilterParameterContainer{
		// hashtable of hashtable used to store filter parameters
		private Hashtable parametersHashtable;
		
		public FilterParameterContainer(){
			parametersHashtable = new Hashtable();
		}// constructor
		
		// set a parameter for a given filter
		public void setParameter(String filterName, String parameter, Object value){
			if((parametersHashtable.get(filterName)) == null){
				Hashtable internalHashTable = new Hashtable();
				internalHashTable.put(parameter, value);
				parametersHashtable.put(filterName, internalHashTable);
			}else{
				((Hashtable)(parametersHashtable.get(filterName))).put(parameter, value);
			}// if
		}// setParameter
		
		// get a parameter for a given filter
		public Object getParameter(String filterName, String parameter){
			Hashtable internalHashTable = (Hashtable)parametersHashtable.get(filterName);
			return internalHashTable.get(parameter);
		}// getParameter
		
		// DEPRECATED
//		/*
//		 * @return Return a String Enumeration of the filter names
//		 */
//		public Enumeration getFilterList(){
//			return parametersHashtable.keys();
//		}// getParametersList
		
		// get parameters and values for a given filter
		public Hashtable getParameterAndValueList(String filterName){
			Hashtable internalHashTable = (Hashtable)parametersHashtable.get(filterName);
			return internalHashTable;
		}// getParameterList
		
		public String toString(){
			String result = new String("\n");
			Enumeration filterNames=parametersHashtable.keys();
			while (filterNames.hasMoreElements()) {
				String currentFilterName = (String)filterNames.nextElement();
				result+=("Key >"+currentFilterName+"\n");
				Hashtable filterParameters = (Hashtable)parametersHashtable.get(currentFilterName);
				    Enumeration filterParameterNames = filterParameters.keys();
					while (filterParameterNames.hasMoreElements()) {
						String currentFilterParameterName = (String)filterParameterNames.nextElement();
						result+=("  SubKey >>"+currentFilterParameterName+"\n");
						Object currentFilterParameterValue = filterParameters.get(currentFilterParameterName);
						result+=("  Value >>"+currentFilterParameterValue.toString()+"\n");
					}// while 
			}// while
			return result;
		}// develListing
		
	}// ParameterContainer
	
	// parameters in the config file
	private String filterList_String;
	private boolean enableCascading;
	private List filterList_List = new ArrayList();
	private List allowClientIp = new ArrayList();
	private Boolean useSecureRequest;
	private List agent = new ArrayList();
	private List httpRequestParameter = new ArrayList();
	private List destinationHost = new ArrayList();
	private String defaultAuthenticationFilter;
	
	// parameters in the request
	private String request_IpAdress;
	private Boolean request_useSecureRequest;
	private String request_agent;
	private List request_parameters = new ArrayList();
	private String request_destinationHost;
	
	// container used to store the parameters of the config file
	// a FilterParameterContainer (see below) is a hashtable containing another hashtable
	// we get something like :
	//	Key >LDAP
	//	 SubKey >>httpRequestParameter - Value >>[identLDAP=true, ident=LDAP]
	//	 SubKey >>useSecureRequest - Value >>false
	//	 SubKey >>agent - Value >>[Mozilla]
	//	 SubKey >>allowClientIp - Value >>[127.0.0.1, 192.168.129.30]
	//	 SubKey >>destinationHost - Value >>[injac.univ.fr:80, localhost:8080]
	//	Key >TRUSTED
	//  ...
	private FilterParameterContainer filterParameterContainer = new FilterParameterContainer();
	
	private Hashtable configMatching = new Hashtable();
	
	// logger
	private static final Log log = LogFactory.getLog(AuthenticationRouter.class);
	
	
	// session variable used to select the authentication filter
	private static final String SELECTED_FILTER = "org.esupportail.filter.authenticationRouter.selectedFilter";
	// list of authentication filters managed by the authentication router - [compulsory]
	private static final String FILTER_LIST = "org.esupportail.filter.authenticationRouter.filterList";
	// cascade or not filters
	private static final String ENABLE_CASCADING = "org.esupportail.filter.authenticationRouter.enableCascading";
	// list of IP adresses allowed to connect the server - [optional]
	private static final String ALLOW_IP = "org.esupportail.filter.authenticationRouter.allowClientIP";
	// if true then https requests needed - [optional]
	private static final String USE_SECURE_REQUEST = "org.esupportail.filter.authenticationRouter.useSecureRequest";
	// list of client types allowed to connect the server - ex: IE, operating system - [optional]
	private static final String AGENT = "org.esupportail.filter.authenticationRouter.agent";
	// list of parameters of the HTTP request - [optional]
	private static final String HTTP_REQUEST = "org.esupportail.filter.authenticationRouter.httpRequestParameter";
	// list of destination virtual hosts - [optional]
	private static final String SERVER = "org.esupportail.filter.authenticationRouter.destinationHost";
	// default filter - one of the values in FILTER_LIST - [optional]
	private static final String DEFAULT_AUTHENTICATION_FILTER = "org.esupportail.filter.authenticationRouter.defaultAuthenticationFilter";
	
	
	// transform a space tabbed String into a List
	private List initList(String s){
		StringTokenizer stringTokenizer = new StringTokenizer(s);
		List list = new ArrayList();
		while (stringTokenizer.hasMoreTokens()) {
			list.add(stringTokenizer.nextElement());
		}// while
		return list;
	}// initList
	
	// transform a space tabbed String into an Enumeration
	private Enumeration initList2(String s){
		String [] temp = s.split(" ");
		Vector v = new Vector();
		
		for (int i=0; i<temp.length; i++){
			v.add(temp[i]);
		}// for (int i=0; i<test.length; i++)
		
		return v.elements();
	}// initList
	
	
	/* (non-Javadoc)
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	public void init(FilterConfig filterConfig) throws ServletException {
		
		filterList_String = filterConfig.getInitParameter(FILTER_LIST);
		
		filterList_List = initList(filterConfig.getInitParameter(FILTER_LIST));
		ListIterator listIterator = filterList_List.listIterator();
		while (listIterator.hasNext()){
			// getting the filter name
			String filterName = (String)listIterator.next();
			// for each filter getting each parameter...
			allowClientIp = initList((filterConfig.getInitParameter(ALLOW_IP+filterName)).toLowerCase());
			useSecureRequest = new Boolean((filterConfig.getInitParameter(USE_SECURE_REQUEST+filterName)).toLowerCase());
			agent = initList((filterConfig.getInitParameter(AGENT+filterName)).toLowerCase());
			httpRequestParameter = initList((filterConfig.getInitParameter(HTTP_REQUEST+filterName)).toLowerCase());
			destinationHost = initList((filterConfig.getInitParameter(SERVER+filterName)).toLowerCase());
			// ...and storing them into the filterParameterContainer
			filterParameterContainer.setParameter(filterName, "allowClientIp", allowClientIp);
			filterParameterContainer.setParameter(filterName, "useSecureRequest", useSecureRequest);
			filterParameterContainer.setParameter(filterName, "agent", agent);
			filterParameterContainer.setParameter(filterName, "httpRequestParameter", httpRequestParameter);
			filterParameterContainer.setParameter(filterName, "destinationHost", destinationHost);
			
			configMatching.put(filterName, Boolean.FALSE);
		}// while
		defaultAuthenticationFilter = filterConfig.getInitParameter(DEFAULT_AUTHENTICATION_FILTER);
		enableCascading = (new Boolean(filterConfig.getInitParameter(ENABLE_CASCADING))).booleanValue();
	}// init
	
	
	/* (non-Javadoc)
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
			
		
		log.info("Entering AuthenticationRouter...");
		
		
		//////////////////////////////////////
		// make sure we've got an HTTP request
		//
		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse))
			throw new ServletException(getClass().getName()+".doFilter() - AuthenticationRouter manages only HTTP resources");
	
		
		////////////////////////////////////////////////////
		// if the user is already authenticated - do nothing
		//
		HttpSession session = ((HttpServletRequest) request).getSession();
		// session id can be useful for detecting authentication problem for example so we log it
		log.debug("Session id is " + session.getId());
		if(session.getAttribute(SELECTED_FILTER)!=null){
			log.debug("Session in progress");
			log.info("Leaving AuthenticationRouter...");
			filterChain.doFilter(request, response);
			return;
		}// if
		
		
		log.debug("Config. file parameters");
		log.debug(filterParameterContainer.toString());
		
		
		/////////////////////////////////////////////
		// getting usefull information in the request :
		//
		//		private String request_IpAdress;
		//		private boolean request_useSecureRequest;
		//		private String request_agent;
		//		private String request_parameters;
		//		private String request_destinationHost;
		log.debug("Request parameters\n");
		request_IpAdress = request.getRemoteAddr().toLowerCase();
		log.debug("request_IpAdress > "+request_IpAdress);
		request_useSecureRequest = new Boolean(request.isSecure());
		log.debug("request_useSecureRequest > "+request_useSecureRequest);
		request_agent = (((HttpServletRequest)request).getHeader("user-agent"));
		//request_agent = request_agent.substring(0, request_agent.indexOf(" ")); // ex : User-agent : Mozilla4.0 (comments)
		request_agent = request_agent.toLowerCase();
		log.debug("request_agent > "+request_agent);
		// building the destinationHost host name
		String request_destinationServerName = request.getServerName().toLowerCase();
		log.debug("request_destinationServerName > "+request_destinationServerName);
		int request_destinationPort = request.getServerPort();
		log.debug("request_destinationPort > "+request_destinationPort);
		request_destinationHost = (request_destinationServerName+":"+request_destinationPort).toLowerCase();
		log.debug("request_destinationHost > "+request_destinationHost);
		
		
		/////////////////////////////////////////////////////////////
		// getting the request parameters and associed values
		// we consider that there is no parameters with the same name
		Enumeration parameterNames = request.getParameterNames();
		while (parameterNames.hasMoreElements()){
			String parameterName = (String)parameterNames.nextElement();
			String parameterValue = request.getParameter(parameterName);
			request_parameters.add(parameterName+"="+parameterValue);
			log.debug("request_parameters > "+parameterName+"="+parameterValue);
		}// while		
		
		
		/////////////////////////////////////////////////////////////////////////
		// for each filter, check if the parameters match with the request or not
		//
		// getting the filter name list
		//Enumeration filterNames=filterParameterContainer.getFilterList(); DEPRECATED
		Enumeration filterNames = initList2(filterList_String);
		// boolean collecting the results of each filter
		// ex : LDAPfilter > param1 match! -> resultCollector = true
		//					 param2 match! -> resultCollector = true
		//					 param3 don't match! -> resultCollector = false
		//					 param4 match! -> resultCollector = false
		// 					 ...
		boolean resultCollector = true;
		boolean oneFilterMatchFound = false; // -Used to quit the following while iteration when one filter matches
											 // -To try to match all the filters (for devel) comment the 2 lignes in the
											 // following while iteration marked with the comment : REMOVE ... TO TRY TO MATCH ALL THE FILTERS
		
		//while (filterNames.hasMoreElements() && !oneFilterMatchFound /* REMOVE THE 2nd CONDITION TO TRY TO MATCH ALL THE FILTERS */) {
		while (filterNames.hasMoreElements() &&  (!oneFilterMatchFound || enableCascading)) {
			
			String currentFilter = (String)filterNames.nextElement();
			log.debug("Filter > "+currentFilter);
			Hashtable filterParameterAndValue = filterParameterContainer.getParameterAndValueList(currentFilter);
				
				/*
				 * if one parameter in the config file is empty then
				 * the request match with the empty parameter then
				 * resultCollector = true;
				 */
				
				// processing the allowClientIp parameter
				allowClientIp = (List)filterParameterAndValue.get("allowClientIp");
				if(allowClientIp!=null && !allowClientIp.isEmpty()){
					resultCollector = allowClientIp.contains(request_IpAdress);
					log.debug("  allowClientIp > "+allowClientIp.contains(request_IpAdress));
				}else{
					resultCollector = true;
					log.debug("  allowClientIp > true");
				}// if
				
				
				// processing the useSecureRequestParameter
				useSecureRequest = (Boolean)filterParameterAndValue.get("useSecureRequest");
				if(useSecureRequest!=null && useSecureRequest.booleanValue()){
					resultCollector = resultCollector && request_useSecureRequest.booleanValue();
					log.debug("  useSecureRequest > "+request_useSecureRequest.booleanValue());
				}else{
					resultCollector = resultCollector && true;
					log.debug("  useSecureRequest > true");
				}// if
				
				
				// processing the agent parameter
				agent = (List)filterParameterAndValue.get("agent");
				Iterator agentIterator = agent.iterator();
				boolean agentResult = false;
				while (agentIterator.hasNext()) {
					String currentAgent = (String) agentIterator.next();
					agentResult = agentResult || request_agent.matches(currentAgent);
				}// while (agentIterator.hasNext())
				
				if(agent!=null && !agent.isEmpty()){
					resultCollector = resultCollector && agentResult;
					log.debug("  agent > "+agentResult);
				}else{
					resultCollector = resultCollector && true;
					log.debug("  agent > true");
				}// if
						
				
				// processing the httpRequestParameter parameter
				httpRequestParameter = (List)filterParameterAndValue.get("httpRequestParameter");
				Iterator httpRequestParameterIterator = httpRequestParameter.iterator();
				Iterator request_parametersIterator = request_parameters.iterator();
				boolean httpRequestParameterResult = false;
//				int nbCurrenthttpRequestParameter = 0;		// for devel
//				int nbCurrentRequest_Parameters = 0;// for devel
				while (httpRequestParameterIterator.hasNext()){
					String currenthttpRequestParameter = (String)httpRequestParameterIterator.next();
					//log.debug("		"+(nbCurrenthttpRequestParameter++)+" currenthttpRequestParameter > "+currenthttpRequestParameter);
					while (request_parametersIterator.hasNext()) {
						String currentRequest_Parameters = (String) request_parametersIterator.next();
						httpRequestParameterResult = httpRequestParameterResult || currentRequest_Parameters.matches(currenthttpRequestParameter);
						//log.debug("			"+(nbCurrentRequest_Parameters++)+" currentRequest_Parameters > "+currentRequest_Parameters);
						//log.debug("			-> httpRequestParameterResult > "+httpRequestParameterResult);
					}// while (request_parametersIterator.hasNext())
				}// while
				
				if(httpRequestParameter!=null && !httpRequestParameter.isEmpty()){
					resultCollector = resultCollector && httpRequestParameterResult;
					log.debug("  httpRequestParameter > "+httpRequestParameterResult);
				}else{
					resultCollector = resultCollector && true;
					log.debug("  httpRequestParameter > true");
				}// if
				
				
			    // processing the destinationHost parameter
				destinationHost = (List)filterParameterAndValue.get("destinationHost");
				Iterator destinationHostIterator = destinationHost.iterator();
				boolean destinationHostResult = false;
				while (destinationHostIterator.hasNext()) {
					String currentDestinationHost = (String) destinationHostIterator.next();
					destinationHostResult = destinationHostResult || request_destinationHost.matches(currentDestinationHost);
				}// while (destinationHostIterator.hasNext())
				
				if(destinationHost!=null && !destinationHost.isEmpty()){
					resultCollector = resultCollector && destinationHostResult;
					log.debug("  destinationHost > "+destinationHostResult);
				}else{
					resultCollector = resultCollector && true;
					log.debug("  destinationHost > true");
				}// if
								
				
				configMatching.put(currentFilter, new Boolean(resultCollector));
				oneFilterMatchFound = resultCollector; //REMOVE THIS LIGNE TO TRY TO MATCH ALL THE FILTERS
				log.debug(("Result for "+currentFilter+" : "+resultCollector));
				resultCollector = true;
				
		}// while
		
		
		////////////////////////////////////
		// Re-initialising request variables
		//
		request_IpAdress = new String();
		request_useSecureRequest = new Boolean(false);
		request_agent = new String();
		request_parameters = new ArrayList();	// only this initialisation is necessary
		request_destinationHost = new String();
		
		
		//////////
		// routing
		//
// TB - 04/10/2005
//		// saving the configMatch hashtable that will be modified
//		Hashtable configMatchingSave = new Hashtable(configMatching);
//		
//		// checking that there is a filter matching with the parameters
//		Collection configMatchingValues = configMatching.values();
//		// so trying the remove the value "true" from the Collection
//		if (configMatchingValues.remove(new Boolean(true))){
//			// here there is one filter matching with the parameters - trying to find another one...
//						
//			filterNames = configMatchingSave.keys(); // variable already initialized - just for comprehension		
//			String chosenFilter = new String("none");
//			while (filterNames.hasMoreElements()){
//				String currentFilter = (String)filterNames.nextElement();
//				Boolean filterResult = (Boolean)configMatchingSave.get(currentFilter);
//				if (filterResult.equals(Boolean.TRUE)){ // true one and only one time
//					chosenFilter = currentFilter;
//				}// if 
//			}// while
//			
//			log.info("Routing to "+chosenFilter);
//			log.info("Leaving AuthenticationRouter...");
//			session.setAttribute(SELECTED_FILTER, chosenFilter);
//			filterChain.doFilter(request, response);
//			return;
//		 TB - 04/10/2005
		
		// checking that there is a filter matching with the parameters
		if (configMatching.containsValue(new Boolean(true))){
			filterNames = configMatching.keys();
			String chosenFilter = new String();
			
			log.debug("Building filter list...");
			while (filterNames.hasMoreElements()){
				String currentFilter = (String)filterNames.nextElement();
				Boolean filterResult = (Boolean)configMatching.get(currentFilter);
				log.debug("currentFilter="+currentFilter+" filterResult="+filterResult);
				if (filterResult.equals(Boolean.TRUE)){
					chosenFilter += currentFilter+":";
					log.debug("chosenFilter="+chosenFilter);
				}// if 
			}// while
			
			// Re-initialising configMatching hashtable
			configMatching = new Hashtable();
			
			//removing trailing :
			if (!chosenFilter.equalsIgnoreCase(""))	chosenFilter = chosenFilter.substring(0, chosenFilter.lastIndexOf(":"));
			log.debug("finally > chosenFilter="+chosenFilter);
			
			log.info("Routing to "+chosenFilter);
			log.info("Leaving AuthenticationRouter...");
			session.setAttribute(SELECTED_FILTER, chosenFilter);
			filterChain.doFilter(request, response);
			return;
			
		}else{
			// there is no filter matching with the parameters
			// routing to the default filter
			log.info("Routing by default to "+defaultAuthenticationFilter);
			log.info("Leaving AuthenticationRouter...");
			session.setAttribute(SELECTED_FILTER, defaultAuthenticationFilter);
			filterChain.doFilter(request, response);
			return;
		}// if
		
	}// doFilter
	

	/* (non-Javadoc)
	 * @see javax.servlet.Filter#destroy()
	 */
	public void destroy() {
		// TODO Auto-generated method stub
		
	}// destroy

	///////////
	// OLD CODE
	//
//	if (configMatchingValues.remove(new Boolean(true))){
//		// here there are two filters matching with the parameters - send a 500 error
//		// internal error
//		log.debug("500 error sent");
//		log.info("Leaving LDAPFilter...");
//		((HttpServletResponse)response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
//		return;
//	}// if
//	// ... only one filter matching with the parameters - finding it and routing...

	
}// AuthenticationRouter
