/******************************************************************************* * * Copyright (c) 2008-2009 Yahoo! Inc. * * 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: * * *******************************************************************************/ package hudson.security.csrf; import hudson.model.Hudson; import java.io.IOException; import java.util.Enumeration; import java.util.logging.Logger; 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; /** * Checks for and validates crumbs on requests that cause state changes, to * protect against cross site request forgeries. * * @author dty */ public class CrumbFilter implements Filter { /** * Because servlet containers generally don't specify the ordering of the * initialization (and different implementations indeed do this differently * --- See HUDSON-3878), we cannot use Hudson to the CrumbIssuer into * CrumbFilter eagerly. */ public CrumbIssuer getCrumbIssuer() { Hudson h = Hudson.getInstance(); if (h == null) { return null; // before Hudson is initialized? } return h.getCrumbIssuer(); } public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { CrumbIssuer crumbIssuer = getCrumbIssuer(); if (crumbIssuer == null || !(request instanceof HttpServletRequest)) { chain.doFilter(request, response); return; } HttpServletRequest httpRequest = (HttpServletRequest) request; String crumbFieldName = crumbIssuer.getDescriptor().getCrumbRequestField(); String crumbSalt = crumbIssuer.getDescriptor().getCrumbSalt(); if ("POST".equals(httpRequest.getMethod())) { String crumb = httpRequest.getHeader(crumbFieldName); boolean valid = false; if (crumb == null) { Enumeration<?> paramNames = request.getParameterNames(); while (paramNames.hasMoreElements()) { String paramName = (String) paramNames.nextElement(); if (crumbFieldName.equals(paramName)) { crumb = request.getParameter(paramName); break; } } } if (crumb != null) { if (crumbIssuer.validateCrumb(httpRequest, crumbSalt, crumb)) { valid = true; } else { LOGGER.warning("Found invalid crumb " + crumb + ". Will check remaining parameters for a valid one..."); } } // Multipart requests need to be handled by each handler. if (valid || isMultipart(httpRequest)) { chain.doFilter(request, response); } else { LOGGER.warning("No valid crumb was included in request for " + httpRequest.getRequestURI() + ". Returning " + HttpServletResponse.SC_FORBIDDEN + "."); HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "No valid crumb was included in the request"); } } else { chain.doFilter(request, response); } } protected static boolean isMultipart(HttpServletRequest request) { if (request == null) { return false; } String contentType = request.getContentType(); if (contentType == null) { return false; } String[] parts = contentType.split(";"); if (parts.length == 0) { return false; } for (int i = 0; i < parts.length; i++) { if ("multipart/form-data".equals(parts[i])) { return true; } } return false; } /** * {@inheritDoc} */ public void destroy() { } private static final Logger LOGGER = Logger.getLogger(CrumbFilter.class.getName()); }