001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.util;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020
021import javax.servlet.http.HttpServletRequest;
022import javax.servlet.http.HttpServletResponse;
023import javax.ws.rs.HttpMethod;
024import java.util.UUID;
025
026/**
027 * Simple utility class that will validate the given request to determine if it has any required CSRF information,
028 * setting appropriate response errors if not.
029 *
030 * @author Eric Westfall
031 */
032public class CsrfValidator {
033
034    private static final Logger LOG = Logger.getLogger(CsrfValidator.class);
035
036    public static final String CSRF_PARAMETER = "csrfToken";
037    public static final String CSRF_SESSION_TOKEN = "csrfSessionToken";
038
039    /**
040     * Applies CSRF protection for any HTTP method other than GET, HEAD, or OPTIONS.
041     *
042     * @param request the http request to check
043     * @param response the http response associated with the given request
044     *
045     * @return true if the request validated successfully, false otherwise. If false is returned, calling code should
046     * act immediately to terminate any additional work performed on the response.
047     */
048    public static boolean validateCsrf(HttpServletRequest request, HttpServletResponse response) {
049        if (HttpMethod.GET.equals(request.getMethod()) ||
050                HttpMethod.HEAD.equals(request.getMethod()) ||
051                HttpMethod.OPTIONS.equals(request.getMethod())) {
052            // if it's a GET and there's not already a CSRF token, then we need to generate and place a CSRF token
053            placeSessionToken(request);
054        } else {
055            String givenCsrf = getRequestToken(request);
056            String actualCsrf = getSessionToken(request);
057            if (actualCsrf == null) {
058                LOG.error("CSRF check failed because no CSRF token has been established on the session");
059                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
060                return false;
061            } else if (!StringUtils.equals(givenCsrf, actualCsrf)) {
062                LOG.error("CSRF check failed, actual value was: " + actualCsrf + ", given value was: " + givenCsrf + ", requested URL was: " + request.getRequestURL());
063                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
064                return false;
065            }
066        }
067        return true;
068    }
069
070
071    /**
072     * Retrieve the CSRF token that is associated with the session for the given request, or null if the session has none.
073     *
074     * @param request the request to check the session for the CSRF token
075     * @return the CSRF token on the request's session, or null if the session has none
076     */
077    public static String getSessionToken(HttpServletRequest request) {
078        return (String)request.getSession().getAttribute(CSRF_SESSION_TOKEN);
079    }
080
081    /**
082     * Retrieve the CSRF token parameter that is on the given request, or null if the request has none.
083     *
084     * @param request the request to check for the CSRF token parameter
085     * @return the CSRF token parameter on the request, or null if the request has none
086     */
087    public static String getRequestToken(HttpServletRequest request) {
088        return request.getParameter(CSRF_PARAMETER);
089    }
090
091    /**
092     * If the session associated with the given request has no CSRF token, this method will generate that token and
093     * add it as an attribute on the session. If the session already has a CSRF token, this method will do nothing.
094     *
095     * @param request the request with the session on which to place the session token if needed
096     */
097    public static void placeSessionToken(HttpServletRequest request) {
098        if (getSessionToken(request) == null) {
099            request.getSession().setAttribute(CSRF_SESSION_TOKEN, UUID.randomUUID().toString());
100        }
101    }
102
103}