package com.atlassian.stash.internal.web.util.xsrf;

import com.atlassian.stash.internal.web.auth.IgnoresXsrf;
import com.atlassian.bitbucket.nav.NavBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

/**
 * Small, self-contained implementation of the XSRF token generator, copied directly from our XWork implementation.
 * Due to Spring MVC being somewhat simplistic (or less generously, pants-on-head stupid) we can't hook into validation
 * the way the XWork implementation does, so we settle ourselves with just returning a bad request status.
 * <p>
 * The interceptor also ensures that the token value is available in the model under the key {@code $xsrfTokenValue}.
 */
public class XsrfTokenInterceptor extends HandlerInterceptorAdapter {
    public static final String XSRF_TOKEN_NAME = "xsrfTokenName";
    public static final String XSRF_TOKEN_VALUE = "xsrfTokenValue";
    public static final String XSRF_FORWARD = "xsrf-forward";
    public static final String XSRF_ORIGINAL_URL = "xsrf-original-url";

    private final NavBuilder navBuilder;
    private final XsrfTokenGenerator xsrfTokenGenerator;

    @Autowired
    public XsrfTokenInterceptor(XsrfTokenGenerator xsrfTokenGenerator, NavBuilder navBuilder) {
        this.xsrfTokenGenerator = xsrfTokenGenerator;
        this.navBuilder = navBuilder;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if ("POST".equals(request.getMethod()) &&
                !isForwarded(request) && // Prevents recursion
                !xsrfTokenGenerator.hasValidToken(request)) {

            if (handler instanceof HandlerMethod) {
                Method method = ((HandlerMethod) handler).getMethod();
                if (method != null && method.getAnnotation(IgnoresXsrf.class) != null) {
                    return true;
                }
            }

            request.setAttribute(XSRF_ORIGINAL_URL, request.getRequestURI());
            request.setAttribute(XSRF_FORWARD, true); // we need to ensure that we only forward the request once
            request.getRequestDispatcher(xsrfUrl()).forward(request, response);
            return false;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (!response.isCommitted()) {
            request.setAttribute(XSRF_TOKEN_NAME, xsrfTokenGenerator.getXsrfTokenName());
            request.setAttribute(XSRF_TOKEN_VALUE, xsrfTokenGenerator.generateToken(request));
        }
    }

    protected String xsrfUrl() {
        return navBuilder.xsrfNotification().buildRelNoContext();
    }

    /**
     * @return {@code true} if the current request has already applied XSRF protection, {@code false} otherwise
     */
    public static boolean isForwarded(HttpServletRequest request) {
        return request.getAttribute(XSRF_FORWARD) != null;
    }

}
