You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by ga...@apache.org on 2016/05/30 12:28:50 UTC

[2/4] incubator-ranger git commit: RANGER-995 : CSRF implementation in Ranger

RANGER-995 : CSRF implementation in Ranger

Signed-off-by: Gautam Borad <ga...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/incubator-ranger/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ranger/commit/e1150005
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ranger/tree/e1150005
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ranger/diff/e1150005

Branch: refs/heads/master
Commit: e11500050d32845441c96adee45d4289624dbf85
Parents: 7d45206
Author: Ankita Sinha <an...@freestoneinfotech.com>
Authored: Wed May 25 12:19:42 2016 +0530
Committer: Gautam Borad <ga...@apache.org>
Committed: Mon May 30 17:58:10 2016 +0530

----------------------------------------------------------------------
 .../org/apache/ranger/rest/ServiceREST.java     |  22 ++
 .../web/filter/RangerCSRFPreventionFilter.java  | 229 +++++++++++++++++++
 .../resources/conf.dist/ranger-admin-site.xml   |  18 ++
 .../conf.dist/security-applicationContext.xml   |   4 +
 security-admin/src/main/webapp/scripts/Main.js  |   3 +-
 .../src/main/webapp/scripts/modules/RestCsrf.js |  98 ++++++++
 .../filter/TestRangerCSRFPreventionFilter.java  | 152 ++++++++++++
 7 files changed, 525 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
----------------------------------------------------------------------
diff --git a/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java b/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
index 052254d..886e78f 100644
--- a/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/ServiceREST.java
@@ -56,6 +56,7 @@ import org.apache.ranger.biz.XUserMgr;
 import org.apache.ranger.common.ContextUtil;
 import org.apache.ranger.common.GUIDUtil;
 import org.apache.ranger.common.MessageEnums;
+import org.apache.ranger.common.PropertiesUtil;
 import org.apache.ranger.common.RESTErrorUtil;
 import org.apache.ranger.common.RangerSearchUtil;
 import org.apache.ranger.common.RangerValidatorFactory;
@@ -114,6 +115,11 @@ public class ServiceREST {
 	private static final String Allowed_User_List_For_Download = "policy.download.auth.users";
 	private static final String Allowed_User_List_For_Grant_Revoke = "policy.grantrevoke.auth.users";
 
+	public static final String isCSRF_ENABLED = "ranger.rest-csrf.enabled";
+	public static final String BROWSER_USER_AGENT_PARAM = "ranger.rest-csrf.browser-useragents-regex";
+	public static final String CUSTOM_METHODS_TO_IGNORE_PARAM = "ranger.rest-csrf.methods-to-ignore";
+	public static final String CUSTOM_HEADER_PARAM = "ranger.rest-csrf.custom-header";
+	
 	@Autowired
 	RESTErrorUtil restErrorUtil;
 
@@ -2248,7 +2254,23 @@ public class ServiceREST {
 	public String checkSSO() {
 		return String.valueOf(bizUtil.isSSOEnabled());
 	}
+	
+	@GET
+	@Path("/csrfconf")
+	@Produces({ "application/json"})
+	public HashMap<String, Object> getCSRFProperties() {
+		return getCSRFPropertiesMap();
+	}
 
+	private HashMap<String, Object> getCSRFPropertiesMap() {
+		HashMap<String, Object> map = new HashMap<String, Object>();  
+		map.put(isCSRF_ENABLED, PropertiesUtil.getBooleanProperty(isCSRF_ENABLED, false));
+		map.put(CUSTOM_HEADER_PARAM, PropertiesUtil.getProperty(CUSTOM_HEADER_PARAM));
+		map.put(BROWSER_USER_AGENT_PARAM, PropertiesUtil.getProperty(BROWSER_USER_AGENT_PARAM));
+		map.put(CUSTOM_METHODS_TO_IGNORE_PARAM, PropertiesUtil.getProperty(CUSTOM_METHODS_TO_IGNORE_PARAM));
+		return map;
+	}
+	
 	boolean isAdminUserWithNoFilterParams(SearchFilter filter) {
 		return (filter == null || MapUtils.isEmpty(filter.getParams())) &&
 			   (bizUtil.isAdmin() || bizUtil.isKeyAdmin());

http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerCSRFPreventionFilter.java
----------------------------------------------------------------------
diff --git a/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerCSRFPreventionFilter.java b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerCSRFPreventionFilter.java
new file mode 100644
index 0000000..42b4ad4
--- /dev/null
+++ b/security-admin/src/main/java/org/apache/ranger/security/web/filter/RangerCSRFPreventionFilter.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.security.web.filter;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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;
+
+import org.apache.log4j.Logger;
+import org.apache.ranger.common.PropertiesUtil;
+
+public class RangerCSRFPreventionFilter implements Filter {
+	
+	private static final Logger LOG = Logger.getLogger(RangerCSRFPreventionFilter.class);
+		
+	public static final boolean isCSRF_ENABLED = PropertiesUtil.getBooleanProperty("ranger.rest-csrf.enabled",true);
+	public static final String BROWSER_USER_AGENT_PARAM = "ranger.rest-csrf.browser-useragents-regex";
+	static final String  BROWSER_USER_AGENTS_DEFAULT = "^Mozilla.*,^Opera.*";
+	public static final String CUSTOM_METHODS_TO_IGNORE_PARAM = "ranger.rest-csrf.methods-to-ignore";
+	static final String  METHODS_TO_IGNORE_DEFAULT = "GET,OPTIONS,HEAD,TRACE";
+	public static final String CUSTOM_HEADER_PARAM = "ranger.rest-csrf.custom-header";
+	public static final String HEADER_DEFAULT = "X-XSRF-HEADER";
+	public static final String HEADER_USER_AGENT = "User-Agent";
+
+	private String  headerName = HEADER_DEFAULT;
+	private Set<String> methodsToIgnore = null;
+	private Set<Pattern> browserUserAgents;
+	
+	public RangerCSRFPreventionFilter() {
+		try {
+			if (isCSRF_ENABLED){
+				init(null);
+			}
+		} catch (Exception e) {
+			LOG.error("Error while initializing Filter : "+e.getMessage());
+		}
+	}
+	
+	public void init(FilterConfig filterConfig) throws ServletException {
+		String customHeader = PropertiesUtil.getProperty(CUSTOM_HEADER_PARAM);
+	    if (customHeader != null) {
+	      headerName = customHeader;
+	    }
+	    
+	    String customMethodsToIgnore = PropertiesUtil.getProperty(CUSTOM_METHODS_TO_IGNORE_PARAM);
+        if (customMethodsToIgnore != null) {
+          parseMethodsToIgnore(customMethodsToIgnore);
+        } else {
+          parseMethodsToIgnore(METHODS_TO_IGNORE_DEFAULT);
+        }
+        String agents = PropertiesUtil.getProperty(BROWSER_USER_AGENT_PARAM);
+        if (agents == null) {
+          agents = BROWSER_USER_AGENTS_DEFAULT;
+        }
+        parseBrowserUserAgents(agents);
+        LOG.info("Adding cross-site request forgery (CSRF) protection");
+	}
+	
+	void parseMethodsToIgnore(String mti) {
+        String[] methods = mti.split(",");
+        methodsToIgnore = new HashSet<String>();
+        for (int i = 0; i < methods.length; i++) {
+          methodsToIgnore.add(methods[i]);
+        }
+	}
+	
+	void parseBrowserUserAgents(String userAgents) {
+		String[] agentsArray = userAgents.split(",");
+		browserUserAgents = new HashSet<Pattern>();
+		for (String patternString : agentsArray) {
+			browserUserAgents.add(Pattern.compile(patternString));
+		}
+	}
+	
+	protected boolean isBrowser(String userAgent) {
+		if (userAgent == null) {
+			return false;
+		}
+		if (browserUserAgents != null){
+			for (Pattern pattern : browserUserAgents) {
+				Matcher matcher = pattern.matcher(userAgent);
+				if (matcher.matches()) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+	  
+	public interface HttpInteraction {
+		/**
+		 * Returns the value of a header.
+		 *
+		 * @param header
+		 *            name of header
+		 * @return value of header
+		 */
+		String getHeader(String header);
+
+		/**
+		 * Returns the method.
+		 *
+		 * @return method
+		 */
+		String getMethod();
+
+		/**
+		 * Called by the filter after it decides that the request may proceed.
+		 *
+		 * @throws IOException
+		 *             if there is an I/O error
+		 * @throws ServletException
+		 *             if the implementation relies on the servlet API and a
+		 *             servlet API call has failed
+		 */
+		void proceed() throws IOException, ServletException;
+
+		/**
+		 * Called by the filter after it decides that the request is a potential
+		 * CSRF attack and therefore must be rejected.
+		 *
+		 * @param code
+		 *            status code to send
+		 * @param message
+		 *            response message
+		 * @throws IOException
+		 *             if there is an I/O error
+		 */
+		void sendError(int code, String message) throws IOException;
+	}	
+	  
+	public void handleHttpInteraction(HttpInteraction httpInteraction)
+			throws IOException, ServletException {
+		if (!isBrowser(httpInteraction.getHeader(HEADER_USER_AGENT))
+				|| methodsToIgnore.contains(httpInteraction.getMethod())
+				|| httpInteraction.getHeader(headerName) != null) {
+			httpInteraction.proceed();
+		}else {
+			httpInteraction.sendError(HttpServletResponse.SC_BAD_REQUEST,"Missing Required Header for CSRF Vulnerability Protection");
+		}
+	}
+	
+	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+		if (isCSRF_ENABLED){
+			final HttpServletRequest httpRequest = (HttpServletRequest)request;
+		    final HttpServletResponse httpResponse = (HttpServletResponse)response;
+		    handleHttpInteraction(new ServletFilterHttpInteraction(httpRequest, httpResponse, chain));
+		}else{
+			chain.doFilter(request, response);
+		}
+	}
+
+	public void destroy() {
+	}
+	
+	private static final class ServletFilterHttpInteraction implements
+			HttpInteraction {
+
+		private final FilterChain chain;
+		private final HttpServletRequest httpRequest;
+		private final HttpServletResponse httpResponse;
+
+		/**
+		 * Creates a new ServletFilterHttpInteraction.
+		 *
+		 * @param httpRequest
+		 *            request to process
+		 * @param httpResponse
+		 *            response to process
+		 * @param chain
+		 *            filter chain to forward to if HTTP interaction is allowed
+		 */
+		public ServletFilterHttpInteraction(HttpServletRequest httpRequest,
+				HttpServletResponse httpResponse, FilterChain chain) {
+			this.httpRequest = httpRequest;
+			this.httpResponse = httpResponse;
+			this.chain = chain;
+		}
+
+		@Override
+		public String getHeader(String header) {
+			return httpRequest.getHeader(header);
+		}
+
+		@Override
+		public String getMethod() {
+			return httpRequest.getMethod();
+		}
+
+		@Override
+		public void proceed() throws IOException, ServletException {
+			chain.doFilter(httpRequest, httpResponse);
+		}
+
+		@Override
+		public void sendError(int code, String message) throws IOException {
+			httpResponse.sendError(code, message);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
----------------------------------------------------------------------
diff --git a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
index c1a91ae..60a2c96 100644
--- a/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
+++ b/security-admin/src/main/resources/conf.dist/ranger-admin-site.xml
@@ -288,4 +288,22 @@
         <name>ranger.kms.service.user.hive</name>
         <value>hive</value>
     </property>
+	<!--  CSRF Properties Starts-->
+	<property>
+		<name>ranger.rest-csrf.enabled</name>
+		<value>true</value>
+	</property>
+	<property>
+		<name>ranger.rest-csrf.custom-header</name>
+		<value>X-XSRF-HEADER</value>
+	</property>
+	<property>
+		<name>ranger.rest-csrf.methods-to-ignore</name>
+		<value>GET,OPTIONS,HEAD,TRACE</value>
+	</property>
+	<property>
+		<name>ranger.rest-csrf.browser-useragents-regex</name>
+		<value>^Mozilla.*,^Opera.*</value>
+	</property>
+	<!--  CSRF Properties ENDs-->
 </configuration>

http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
----------------------------------------------------------------------
diff --git a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
index 66ef8af..13ddb26 100644
--- a/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
+++ b/security-admin/src/main/resources/conf.dist/security-applicationContext.xml
@@ -50,6 +50,7 @@ http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd">
 		<intercept-url pattern="/**" access="isAuthenticated()"/>       
 		<custom-filter ref="ssoAuthenticationFilter" after="BASIC_AUTH_FILTER" /> 
 		<security:custom-filter ref="krbAuthenticationFilter" after="SERVLET_API_SUPPORT_FILTER" />
+		<security:custom-filter ref="CSRFPreventionFilter" after="REMEMBER_ME_FILTER" />
 		<security:custom-filter position="FORM_LOGIN_FILTER" ref="customUsernamePasswordAuthenticationFilter"/>
 		<security:custom-filter position="LAST" ref="userContextFormationFilter"/>
 
@@ -93,6 +94,9 @@ http://www.springframework.org/schema/security/spring-security-oauth2-1.0.xsd">
 	<beans:bean id="krbAuthenticationFilter" class="org.apache.ranger.security.web.filter.RangerKRBAuthenticationFilter">
     </beans:bean>
 
+	<beans:bean id="CSRFPreventionFilter" class="org.apache.ranger.security.web.filter.RangerCSRFPreventionFilter">
+    </beans:bean>
+
     <beans:bean id="ssoAuthenticationFilter" class="org.apache.ranger.security.web.filter.RangerSSOAuthenticationFilter">
     </beans:bean>
 	

http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/main/webapp/scripts/Main.js
----------------------------------------------------------------------
diff --git a/security-admin/src/main/webapp/scripts/Main.js b/security-admin/src/main/webapp/scripts/Main.js
index 460c91a..d518afb 100644
--- a/security-admin/src/main/webapp/scripts/Main.js
+++ b/security-admin/src/main/webapp/scripts/Main.js
@@ -24,10 +24,11 @@
 	'routers/Router',
 	'controllers/Controller',
 	'modules/XAOverrides',
+	'modules/RestCsrf',
 	'utils/XAUtils',
 	'hbs!tmpl/common/loading_tmpl'
 ],
-function ( Backbone, App, RegionManager, AppRouter, AppController, XAOverrides, XAUtils, loadingHTML ) {
+function ( Backbone, App, RegionManager, AppRouter, AppController, XAOverrides,RestCSRF, XAUtils, loadingHTML ) {
     'use strict';
 
     var controller = new AppController();

http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/main/webapp/scripts/modules/RestCsrf.js
----------------------------------------------------------------------
diff --git a/security-admin/src/main/webapp/scripts/modules/RestCsrf.js b/security-admin/src/main/webapp/scripts/modules/RestCsrf.js
new file mode 100644
index 0000000..2eff355
--- /dev/null
+++ b/security-admin/src/main/webapp/scripts/modules/RestCsrf.js
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//"use strict";
+
+// Initializes client-side handling of cross-site request forgery (CSRF)
+// protection by figuring out the custom HTTP headers that need to be sent in
+// requests and which HTTP methods are ignored because they do not require CSRF
+// protection.
+(function() {
+	"use strict";
+	require('jquery');
+	var restCsrfCustomHeader = null;
+	var restCsrfMethodsToIgnore = null;
+
+	if(!window.location.origin){
+		window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
+	}
+	var baseUrl = window.location.origin +
+  					window.location.pathname.substring(window.location.pathname.indexOf('/', 2) + 1, 0);
+	if(baseUrl.slice(-1) == "/") {
+	  baseUrl = baseUrl.slice(0,-1);
+	}
+	var url = baseUrl + "/service/plugins/csrfconf";
+
+  $.ajax({'url': url, 'dataType': 'json', 'async': false}).done(
+    function(data) {
+    	function getTrimmedStringArrayValue(element) {
+    		var str = element, array = [];
+    		if (str) {
+    			var splitStr = str.split(',');
+    			for (var i = 0; i < splitStr.length; i++) {
+    				array.push(splitStr[i].trim());
+    			}
+    		}
+    		return array;
+      }
+
+      // Get all relevant configuration properties.
+      var $xml = $(data);
+      var csrfEnabled = false;
+      var header = null;
+      var methods = [];
+      $xml.each(function(indx,element){
+    	  if(element['ranger.rest-csrf.enabled']) {
+    		  var str = "" + element['ranger.rest-csrf.enabled'];
+    		  csrfEnabled = (str.toLowerCase() == 'true');
+    	  }
+    	  if (element['ranger.rest-csrf.custom-header']) {
+    		  header = element['ranger.rest-csrf.custom-header'].trim();
+    	  }
+    	  if (element['ranger.rest-csrf.methods-to-ignore']) {
+    		  methods = getTrimmedStringArrayValue(element['ranger.rest-csrf.methods-to-ignore']);
+    	  }
+      });
+
+      // If enabled, set up all subsequent AJAX calls with a pre-send callback
+      // that adds the custom headers if necessary.
+      if (csrfEnabled) {
+        restCsrfCustomHeader = header;
+        restCsrfMethodsToIgnore = {};
+        methods.map(function(method) { restCsrfMethodsToIgnore[method] = true; });
+        $.ajaxSetup({
+          beforeSend: addRestCsrfCustomHeader
+        });
+      }
+    });
+
+  // Adds custom headers to request if necessary.  This is done only for WebHDFS
+  // URLs, and only if it's not an ignored method.
+  function addRestCsrfCustomHeader(xhr, settings) {
+//    if (settings.url == null || !settings.url.startsWith('/webhdfs/')) {
+	  if (settings.url == null ) {
+      return;
+    }
+    var method = settings.type;
+    if (restCsrfCustomHeader != null && !restCsrfMethodsToIgnore[method]) {
+      // The value of the header is unimportant.  Only its presence matters.
+      xhr.setRequestHeader(restCsrfCustomHeader, '""');
+    }
+  }
+})();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-ranger/blob/e1150005/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerCSRFPreventionFilter.java
----------------------------------------------------------------------
diff --git a/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerCSRFPreventionFilter.java b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerCSRFPreventionFilter.java
new file mode 100644
index 0000000..f15def4
--- /dev/null
+++ b/security-admin/src/test/java/org/apache/ranger/security/web/filter/TestRangerCSRFPreventionFilter.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.security.web.filter;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+
+public class TestRangerCSRFPreventionFilter {
+	
+	private static final String EXPECTED_MESSAGE = "Missing Required Header for CSRF Vulnerability Protection";
+	private static final String X_CUSTOM_HEADER = "X-CUSTOM_HEADER";
+	private String userAgent = "Mozilla";
+	
+	@Test
+	public void testNoHeaderDefaultConfig_badRequest() throws ServletException, IOException {
+		// CSRF has not been sent
+		HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_DEFAULT)).thenReturn(null);
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_USER_AGENT)).thenReturn(userAgent);		
+
+		// Objects to verify interactions based on request
+		HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+		FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+		// Object under test
+		RangerCSRFPreventionFilter filter = new RangerCSRFPreventionFilter();
+		filter.doFilter(mockReq, mockRes, mockChain);
+
+		verify(mockRes, atLeastOnce()).sendError(HttpServletResponse.SC_BAD_REQUEST, EXPECTED_MESSAGE);
+		Mockito.verifyZeroInteractions(mockChain);
+	}
+	
+	@Test
+	public void testHeaderPresentDefaultConfig_goodRequest() throws ServletException, IOException {
+		// CSRF HAS been sent
+		HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_DEFAULT)).thenReturn("valueUnimportant");
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_USER_AGENT)).thenReturn(userAgent);
+
+		// Objects to verify interactions based on request
+		HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+		FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+		// Object under test
+		RangerCSRFPreventionFilter filter = new RangerCSRFPreventionFilter();
+		filter.doFilter(mockReq, mockRes, mockChain);
+
+		Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+	}
+
+	@Test
+	public void testHeaderPresentCustomHeaderConfig_goodRequest() throws ServletException, IOException {
+		// CSRF HAS been sent
+		HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+		Mockito.when(mockReq.getHeader(X_CUSTOM_HEADER)).thenReturn("valueUnimportant");
+
+		// Objects to verify interactions based on request
+		HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+		FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+		// Object under test
+		RangerCSRFPreventionFilter filter = new RangerCSRFPreventionFilter();
+		filter.doFilter(mockReq, mockRes, mockChain);
+
+		Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+	}
+
+	@Test
+	public void testMissingHeaderWithCustomHeaderConfig_badRequest() throws ServletException, IOException {
+		// CSRF has not been sent
+		HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+		Mockito.when(mockReq.getHeader(X_CUSTOM_HEADER)).thenReturn(null);
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_USER_AGENT)).thenReturn(userAgent);
+
+		// Objects to verify interactions based on request
+		HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+		FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+		// Object under test
+		RangerCSRFPreventionFilter filter = new RangerCSRFPreventionFilter();
+		filter.doFilter(mockReq, mockRes, mockChain);
+
+		Mockito.verifyZeroInteractions(mockChain);
+	}
+
+	@Test
+	public void testMissingHeaderIgnoreGETMethodConfig_goodRequest()
+			throws ServletException, IOException {
+		// CSRF has not been sent
+		HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_DEFAULT)).thenReturn(null);
+		Mockito.when(mockReq.getMethod()).thenReturn("GET");
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_USER_AGENT)).thenReturn(userAgent);
+
+		// Objects to verify interactions based on request
+		HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+		FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+		// Object under test
+		RangerCSRFPreventionFilter filter = new RangerCSRFPreventionFilter();
+		filter.doFilter(mockReq, mockRes, mockChain);
+
+		Mockito.verify(mockChain).doFilter(mockReq, mockRes);
+	}
+
+	@Test
+	public void testMissingHeaderMultipleIgnoreMethodsConfig_badRequest()
+			throws ServletException, IOException {
+		// CSRF has not been sent
+		HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_DEFAULT))
+				.thenReturn(null);
+		Mockito.when(mockReq.getMethod()).thenReturn("PUT");
+		Mockito.when(mockReq.getHeader(RangerCSRFPreventionFilter.HEADER_USER_AGENT)).thenReturn(userAgent);
+
+		// Objects to verify interactions based on request
+		HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+		FilterChain mockChain = Mockito.mock(FilterChain.class);
+
+		// Object under test
+		RangerCSRFPreventionFilter filter = new RangerCSRFPreventionFilter();
+		filter.doFilter(mockReq, mockRes, mockChain);
+
+		Mockito.verifyZeroInteractions(mockChain);
+	}
+}
\ No newline at end of file