org.eclipse.orion.server.servlets.XSRFPreventionFilter.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.orion.server.servlets.XSRFPreventionFilter.java

Source

/*******************************************************************************
 * Copyright (c) 2014 SAP AG and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     SAP AG - initial API and implementation
 *******************************************************************************/
package org.eclipse.orion.server.servlets;

import java.io.IOException;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.orion.server.core.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A filter that implements XSRF protection via double submit cookies.
 */
public class XSRFPreventionFilter implements Filter {

    private static final String NONCES_DO_NOT_MATCH = "{0} {1} on behalf of user ''{2}'': CSRF tokens do not match: ''{3}'' does not equal ''{4}''";
    private static final String NO_NONCE_IN_HEADER = "{0} {1} on behalf of user ''{2}'': missing CSRF token in header.";
    private static final String NO_NONCE_IN_COOKIES = "{0} {1} on behalf of user ''{2}'': missing CSRF token in cookies.";

    private static final Logger LOG = LoggerFactory.getLogger(XSRFPreventionFilter.class);

    private static final String XSRF_TOKEN = "x-csrf-token";//$NON-NLS-1$

    private final Set<String> entryPointList = new HashSet<String>();
    private final Set<String> exceptionList = new HashSet<String>();

    private SecureRandom secureRandom;

    private boolean xsrfPreventionFilterDisabled = false;

    public void init(FilterConfig filterConfig) throws ServletException {
        entryPointList.add("/login");//$NON-NLS-1$

        exceptionList.add("/login");//$NON-NLS-1$
        exceptionList.add("/login/canaddusers");//$NON-NLS-1$
        exceptionList.add("/login/form");//$NON-NLS-1$
        exceptionList.add("/useremailconfirmation/cansendemails");//$NON-NLS-1$

        secureRandom = new SecureRandom();
        secureRandom.nextBytes(new byte[1]);

        String enableCSRF = PreferenceHelper.getString(ServerConstants.CONFIG_XSRF_PROTECTION_ENABLED);
        xsrfPreventionFilterDisabled = !Boolean.parseBoolean(enableCSRF);
    }

    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        if (xsrfPreventionFilterDisabled) {
            chain.doFilter(req, resp);
            return;
        }

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        String method = request.getMethod();

        String path = request.getServletPath();
        if (request.getPathInfo() != null) {
            path = path + request.getPathInfo();
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug(MessageFormat.format("Filter called for {0} {1}. ", method, path));
        }

        // check if nonce should be generated
        CookieHandler ch = new CookieHandler(request.getCookies(), XSRF_TOKEN);
        if (isEntryPoint(req, path) && !ch.hasNonceCookie()) {
            response.addCookie(new Cookie(XSRF_TOKEN, generateNonce(method, path)));
        }

        boolean doNonceCheck = !"get".equalsIgnoreCase(method) && !isException(req, path);//$NON-NLS-1$
        if (doNonceCheck) {
            String requestNonce = request.getHeader(XSRF_TOKEN);
            boolean nonceValid = checkNonce(method, path, ch, requestNonce);

            if (!nonceValid) {
                logReasonForInvalidNonce(request, method, path, ch, requestNonce);
                prepareResponseForInvalidNonce(response);
                return;
            }
        } else if (LOG.isDebugEnabled()) {
            LOG.debug(MessageFormat.format("Skipping nonce check for {0} {1}", method, path));
        }

        chain.doFilter(request, response);
    }

    private boolean isEntryPoint(ServletRequest req, String path) {
        if (entryPointList.contains(path))
            return true;
        // Self-hosting check
        return entryPointList.contains((String) req.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
    }

    private boolean isException(ServletRequest req, String path) {
        if (exceptionList.contains(path))
            return true;
        // Self-hosting check
        return exceptionList.contains((String) req.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
    }

    public void destroy() {
        // do nothing
    }

    private void prepareResponseForInvalidNonce(HttpServletResponse response) throws IOException {
        response.setHeader(XSRF_TOKEN, "required");//$NON-NLS-1$
        ServerStatus status = new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN, "Access Denied",
                null);
        response.setContentType("application/json");//$NON-NLS-1$
        response.setStatus(status.getHttpCode());
        response.getWriter().print(status.toJSON().toString());
    }

    private void logReasonForInvalidNonce(HttpServletRequest request, String method, String path, CookieHandler ch,
            String requestNonce) {
        if (ch.hasNonceCookie() && (requestNonce != null)) {
            LOG.error(MessageFormat.format(NONCES_DO_NOT_MATCH, method, path, request.getRemoteUser(), requestNonce,
                    ch.getValue()));
        } else {
            if (!ch.hasNonceCookie()) {
                LOG.error(MessageFormat.format(NO_NONCE_IN_COOKIES, method, path, request.getRemoteUser()));
            }
            if (requestNonce == null) {
                LOG.error(MessageFormat.format(NO_NONCE_IN_HEADER, method, path, request.getRemoteUser()));
            }
        }
    }

    private boolean checkNonce(String method, String path, CookieHandler ch, String requestNonce) {
        boolean nonceValid = false;
        if (ch.hasNonceCookie()) {
            nonceValid = ch.getValue().equals(requestNonce);
        }
        return nonceValid;
    }

    private String generateNonce(String method, String path) {
        byte[] randomBytes = new byte[24];
        secureRandom.nextBytes(randomBytes);
        String nonce = Base64.encodeBase64URLSafeString(randomBytes);
        if (LOG.isDebugEnabled()) {
            LOG.debug(MessageFormat.format("Creating nonce  for {0} {1}: ''{2}''", method, path, nonce));
        }
        return nonce;
    }

    private static class CookieHandler {
        private Cookie cookie;

        public CookieHandler(Cookie[] cookies, String name) {
            if (cookies == null)
                return;
            for (Cookie c : cookies) {
                if (name.equals(c.getName())) {
                    cookie = c;
                    break;
                }
            }
        }

        public String getValue() {
            return cookie.getValue();
        }

        public boolean hasNonceCookie() {
            return cookie != null;
        }
    }
}