package it.mice.voila.runtime.springmvc.csrf;

import it.mice.voila.runtime.exception.BusinessException;
import it.mice.voila.runtime.springmvc.bean.PathStack;
import it.mice.voila.runtime.springmvc.bean.PathStackElement;
import it.mice.voila.runtime.springmvc.controller.ManageController;
import it.mice.voila.runtime.springmvc.controller.PopupController;
import it.mice.voila.runtime.springmvc.taglibs.TagUtils;
import it.mice.voila.runtime.springmvc.util.SpringMvcUtil;
import it.mice.voila.runtime.util.UserMessage;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.springframework.web.util.HtmlUtils;

/**
 * A Spring MVC <code>HandlerInterceptor</code> which is responsible to enforce CSRF token validity on incoming posts requests. The interceptor
 * should be registered with Spring MVC servlet using the following syntax:
 * <pre>
 *   &lt;mvc:interceptors&gt;
 *        &lt;bean class="it.mice.voila.runtime.springmvc.csrf.CSRFHandlerInterceptor"/&gt;
 *   &lt;/mvc:interceptors&gt;
 *   </pre>
 * @author Eyal Lupu
 * @see CSRFRequestDataValueProcessor
 *
 */
public class CSRFHandlerInterceptor extends HandlerInterceptorAdapter{

    /**
     * Default logger.
     */
    private static final Log LOGGER = LogFactory.getLog(CSRFHandlerInterceptor.class); // NOSONAR

    private Collection<String> excludedParameters = new ArrayList<String>();
    
	public Collection<String> getExcludedParameters() {
		return excludedParameters;
	}

	public void setExcludedParameters(Collection<String> excludedParameters) {
		this.excludedParameters = excludedParameters;
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		if (handler instanceof ResourceHttpRequestHandler) {
			return true;
		}
		boolean result = false;
		Object handlerBean = SpringMvcUtil.getHandlerBean(handler);
		if (handlerBean instanceof PopupController) {
			request.setAttribute(TagUtils.SECURITY_SESSION_MODE, "_POPUP");
		} else if (handlerBean instanceof ManageController) {
			request.setAttribute(TagUtils.SECURITY_SESSION_MODE, "_CONTROLLER");
		} else {
			return true;
		}
		if (!request.getMethod().equalsIgnoreCase("POST")) {
			// Not a POST or not a manage controller - allow the request
			result = true;
		} else {
			// This is a POST request - need to check the CSRF token
			String sessionToken = CSRFTokenManager.getTokenForSession(request.getSession());
			String requestToken = CSRFTokenManager.getTokenFromRequest(request);
			if (sessionToken.equals(requestToken)) {
				result = checkParameters(request, response, handler);
			} else {
				throw new BusinessException(new UserMessage("error.security.unauthorizedRequest"));
				//response.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad or missing CSRF value");
				//result = false;
			}
		}
		return result;
	}

	private boolean checkParameters(HttpServletRequest request,
			HttpServletResponse response, Object handler) throws Exception {
		
		SecuritySessionBean ssb = TagUtils.getSecuritySessionBean();
		//TagUtils.removeSecuritySessionBean();
		Enumeration<String> e = request.getParameterNames();
		while (e.hasMoreElements()) {
			String paramName = e.nextElement();
			if (!CSRFTokenManager.CSRF_PARAM_NAME.equals(paramName) && !isExcludedParameter(paramName)) { //FIXME
				FormField ff = ssb.getFormFields().get(paramName);
				if (ff == null) {
					LOGGER.warn("POST request field " + paramName + " not allowed.");
					throw new BusinessException(new UserMessage("error.security.unauthorizedRequest"));
					//response.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad form field parameter");
					//return false;
				} else {
					if (!ff.getParameterValue().isEmpty()) {
						boolean valueCheck = false;
						String paramValue = request.getParameter(paramName);
						for (String value : ff.getParameterValue()) {
							if (checkSameValue(paramValue, value, ff.getParameterType(), ff.getActualValue())) {
								valueCheck = true;
								break;
							}
						}
						if (!valueCheck) {
							LOGGER.warn("POST request field " + paramName + " not allowed value=" + paramValue);
							throw new BusinessException(new UserMessage("error.security.unauthorizedRequest"));
							//response.sendError(HttpServletResponse.SC_FORBIDDEN, "Bad form field parameter value");
							//return false;
						}
					}
				}
			}
		}
		return true;
	}

	private boolean isExcludedParameter(String paramName) {
		for (String ex : getExcludedParameters()) {
			if (paramName.startsWith(ex)) {
				return true;
			}
		}
		return false;
	}
	
	protected boolean checkSameValue(String paramValue, String savedValue, String type, Object actualValue) {
		if (paramValue.equals(savedValue)) {
			return true;
		}
		if (HtmlUtils.htmlEscape(paramValue).equals(savedValue)) {
			return true;
		}
		if (actualValue != null) {
			try {
				return new BigDecimal(paramValue).compareTo(new BigDecimal(actualValue.toString())) == 0;
			} catch (Exception e) {
				return false;
			}
		}
		return false;
	}

}
