RANGER-995 : CSRF implementation in Ranger Signed-off-by: Gautam Borad <[email protected]>
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 <[email protected]> Authored: Wed May 25 12:19:42 2016 +0530 Committer: Gautam Borad <[email protected]> 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
