Repository: hadoop Updated Branches: refs/heads/branch-2.8 a06036bb1 -> e5c8a3444
HADOOP-12691. Move files to correct location. (cherry picked from commit da77f423d142c4dda8810d4668edde3c7d2999e8) (cherry picked from commit 2b9ea68ef8fbca524571eae27672323b4910464f) Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/2e047429 Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/2e047429 Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/2e047429 Branch: refs/heads/branch-2.8 Commit: 2e047429be1916894c503a0b3cb64e1636c6228a Parents: a06036b Author: cnauroth <[email protected]> Authored: Sat Jan 16 07:47:37 2016 -0800 Committer: cnauroth <[email protected]> Committed: Thu Feb 18 10:15:31 2016 -0800 ---------------------------------------------------------------------- .../security/http/RestCsrfPreventionFilter.java | 139 ++++++++ .../http/TestRestCsrfPreventionFilter.java | 357 +++++++++++++++++++ .../security/http/RestCsrfPreventionFilter.java | 139 -------- .../http/TestRestCsrfPreventionFilter.java | 357 ------------------- 4 files changed, 496 insertions(+), 496 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/2e047429/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java new file mode 100644 index 0000000..4f7f5bb --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java @@ -0,0 +1,139 @@ +/** + * 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.hadoop.security.http; + +import java.io.IOException; +import java.util.HashSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.Set; + +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; + +/** + * This filter provides protection against cross site request forgery (CSRF) + * attacks for REST APIs. Enabling this filter on an endpoint results in the + * requirement of all client to send a particular (configurable) HTTP header + * with every request. In the absense of this header the filter will reject the + * attempt as a bad request. + */ +public class RestCsrfPreventionFilter implements Filter { + public static final String HEADER_USER_AGENT = "User-Agent"; + public static final String BROWSER_USER_AGENT_PARAM = + "browser-useragents-regex"; + public static final String CUSTOM_HEADER_PARAM = "custom-header"; + public static final String CUSTOM_METHODS_TO_IGNORE_PARAM = + "methods-to-ignore"; + static final String BROWSER_USER_AGENTS_DEFAULT = "^Mozilla.*,^Opera.*"; + static final String HEADER_DEFAULT = "X-XSRF-HEADER"; + static final String METHODS_TO_IGNORE_DEFAULT = "GET,OPTIONS,HEAD,TRACE"; + private String headerName = HEADER_DEFAULT; + private Set<String> methodsToIgnore = null; + private Set<Pattern> browserUserAgents; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String customHeader = filterConfig.getInitParameter(CUSTOM_HEADER_PARAM); + if (customHeader != null) { + headerName = customHeader; + } + String customMethodsToIgnore = + filterConfig.getInitParameter(CUSTOM_METHODS_TO_IGNORE_PARAM); + if (customMethodsToIgnore != null) { + parseMethodsToIgnore(customMethodsToIgnore); + } else { + parseMethodsToIgnore(METHODS_TO_IGNORE_DEFAULT); + } + + String agents = filterConfig.getInitParameter(BROWSER_USER_AGENT_PARAM); + if (agents == null) { + agents = BROWSER_USER_AGENTS_DEFAULT; + } + parseBrowserUserAgents(agents); + } + + void parseBrowserUserAgents(String userAgents) { + String[] agentsArray = userAgents.split(","); + browserUserAgents = new HashSet<Pattern>(); + for (String patternString : agentsArray) { + browserUserAgents.add(Pattern.compile(patternString)); + } + } + + void parseMethodsToIgnore(String mti) { + String[] methods = mti.split(","); + methodsToIgnore = new HashSet<String>(); + for (int i = 0; i < methods.length; i++) { + methodsToIgnore.add(methods[i]); + } + } + + /** + * This method interrogates the User-Agent String and returns whether it + * refers to a browser. If its not a browser, then the requirement for the + * CSRF header will not be enforced; if it is a browser, the requirement will + * be enforced. + * <p> + * A User-Agent String is considered to be a browser if it matches + * any of the regex patterns from browser-useragent-regex; the default + * behavior is to consider everything a browser that matches the following: + * "^Mozilla.*,^Opera.*". Subclasses can optionally override + * this method to use different behavior. + * + * @param userAgent The User-Agent String, or null if there isn't one + * @return true if the User-Agent String refers to a browser, false if not + */ + protected boolean isBrowser(String userAgent) { + if (userAgent == null) { + return false; + } + for (Pattern pattern : browserUserAgents) { + Matcher matcher = pattern.matcher(userAgent); + if (matcher.matches()) { + return true; + } + } + return false; + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest)request; + if (!isBrowser(httpRequest.getHeader(HEADER_USER_AGENT)) || + methodsToIgnore.contains(httpRequest.getMethod()) || + httpRequest.getHeader(headerName) != null) { + chain.doFilter(request, response); + } else { + ((HttpServletResponse)response).sendError( + HttpServletResponse.SC_BAD_REQUEST, + "Missing Required Header for CSRF Vulnerability Protection"); + } + } + + @Override + public void destroy() { + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/2e047429/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java new file mode 100644 index 0000000..29dccd3 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java @@ -0,0 +1,357 @@ +/** + * 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.hadoop.security.http; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Test; +import org.mockito.Mockito; + +public class TestRestCsrfPreventionFilter { + + private static final String NON_BROWSER = "java"; + private static final String BROWSER_AGENT = + "Mozilla/5.0 (compatible; U; ABrowse 0.6; Syllable)" + + " AppleWebKit/420+ (KHTML, like Gecko)"; + private static final String EXPECTED_MESSAGE = + "Missing Required Header for CSRF Vulnerability Protection"; + private static final String X_CUSTOM_HEADER = "X-CUSTOM_HEADER"; + + @Test + public void testNoHeaderDefaultConfig_badRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(null); + + // CSRF has not been sent + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(BROWSER_AGENT); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + verify(mockRes, atLeastOnce()).sendError( + HttpServletResponse.SC_BAD_REQUEST, EXPECTED_MESSAGE); + Mockito.verifyZeroInteractions(mockChain); + } + + @Test + public void testNoHeaderCustomAgentConfig_badRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.BROWSER_USER_AGENT_PARAM)). + thenReturn("^Mozilla.*,^Opera.*,curl"); + + // CSRF has not been sent + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn("curl"); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + verify(mockRes, atLeastOnce()).sendError( + HttpServletResponse.SC_BAD_REQUEST, EXPECTED_MESSAGE); + Mockito.verifyZeroInteractions(mockChain); + } + + @Test + public void testNoHeaderDefaultConfigNonBrowser_goodRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(null); + + // CSRF has not been sent + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(NON_BROWSER); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testHeaderPresentDefaultConfig_goodRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(null); + + // CSRF HAS been sent + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn("valueUnimportant"); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testHeaderPresentCustomHeaderConfig_goodRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)). + thenReturn(X_CUSTOM_HEADER); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(null); + + // 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 + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testMissingHeaderWithCustomHeaderConfig_badRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)). + thenReturn(X_CUSTOM_HEADER); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(null); + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(BROWSER_AGENT); + + // CSRF has not been sent + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verifyZeroInteractions(mockChain); + } + + @Test + public void testMissingHeaderNoMethodsToIgnoreConfig_badRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn(""); + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(BROWSER_AGENT); + + // CSRF has not been sent + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getMethod()). + thenReturn("GET"); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verifyZeroInteractions(mockChain); + } + + @Test + public void testMissingHeaderIgnoreGETMethodConfig_goodRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn("GET"); + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(BROWSER_AGENT); + + // CSRF has not been sent + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getMethod()). + thenReturn("GET"); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testMissingHeaderMultipleIgnoreMethodsConfig_goodRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn("GET,OPTIONS"); + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(BROWSER_AGENT); + + // CSRF has not been sent + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getMethod()). + thenReturn("OPTIONS"); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testMissingHeaderMultipleIgnoreMethodsConfig_badRequest() + throws ServletException, IOException { + // Setup the configuration settings of the server + FilterConfig filterConfig = Mockito.mock(FilterConfig.class); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); + Mockito.when(filterConfig.getInitParameter( + RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). + thenReturn("GET,OPTIONS"); + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). + thenReturn(BROWSER_AGENT); + + // CSRF has not been sent + Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). + thenReturn(null); + Mockito.when(mockReq.getMethod()). + thenReturn("PUT"); + + // Objects to verify interactions based on request + HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); + FilterChain mockChain = Mockito.mock(FilterChain.class); + + // Object under test + RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); + filter.init(filterConfig); + filter.doFilter(mockReq, mockRes, mockChain); + + Mockito.verifyZeroInteractions(mockChain); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/2e047429/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java b/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java deleted file mode 100644 index 4f7f5bb..0000000 --- a/hadoop-common/src/main/java/org/apache/hadoop/security/http/RestCsrfPreventionFilter.java +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 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.hadoop.security.http; - -import java.io.IOException; -import java.util.HashSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.Set; - -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; - -/** - * This filter provides protection against cross site request forgery (CSRF) - * attacks for REST APIs. Enabling this filter on an endpoint results in the - * requirement of all client to send a particular (configurable) HTTP header - * with every request. In the absense of this header the filter will reject the - * attempt as a bad request. - */ -public class RestCsrfPreventionFilter implements Filter { - public static final String HEADER_USER_AGENT = "User-Agent"; - public static final String BROWSER_USER_AGENT_PARAM = - "browser-useragents-regex"; - public static final String CUSTOM_HEADER_PARAM = "custom-header"; - public static final String CUSTOM_METHODS_TO_IGNORE_PARAM = - "methods-to-ignore"; - static final String BROWSER_USER_AGENTS_DEFAULT = "^Mozilla.*,^Opera.*"; - static final String HEADER_DEFAULT = "X-XSRF-HEADER"; - static final String METHODS_TO_IGNORE_DEFAULT = "GET,OPTIONS,HEAD,TRACE"; - private String headerName = HEADER_DEFAULT; - private Set<String> methodsToIgnore = null; - private Set<Pattern> browserUserAgents; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - String customHeader = filterConfig.getInitParameter(CUSTOM_HEADER_PARAM); - if (customHeader != null) { - headerName = customHeader; - } - String customMethodsToIgnore = - filterConfig.getInitParameter(CUSTOM_METHODS_TO_IGNORE_PARAM); - if (customMethodsToIgnore != null) { - parseMethodsToIgnore(customMethodsToIgnore); - } else { - parseMethodsToIgnore(METHODS_TO_IGNORE_DEFAULT); - } - - String agents = filterConfig.getInitParameter(BROWSER_USER_AGENT_PARAM); - if (agents == null) { - agents = BROWSER_USER_AGENTS_DEFAULT; - } - parseBrowserUserAgents(agents); - } - - void parseBrowserUserAgents(String userAgents) { - String[] agentsArray = userAgents.split(","); - browserUserAgents = new HashSet<Pattern>(); - for (String patternString : agentsArray) { - browserUserAgents.add(Pattern.compile(patternString)); - } - } - - void parseMethodsToIgnore(String mti) { - String[] methods = mti.split(","); - methodsToIgnore = new HashSet<String>(); - for (int i = 0; i < methods.length; i++) { - methodsToIgnore.add(methods[i]); - } - } - - /** - * This method interrogates the User-Agent String and returns whether it - * refers to a browser. If its not a browser, then the requirement for the - * CSRF header will not be enforced; if it is a browser, the requirement will - * be enforced. - * <p> - * A User-Agent String is considered to be a browser if it matches - * any of the regex patterns from browser-useragent-regex; the default - * behavior is to consider everything a browser that matches the following: - * "^Mozilla.*,^Opera.*". Subclasses can optionally override - * this method to use different behavior. - * - * @param userAgent The User-Agent String, or null if there isn't one - * @return true if the User-Agent String refers to a browser, false if not - */ - protected boolean isBrowser(String userAgent) { - if (userAgent == null) { - return false; - } - for (Pattern pattern : browserUserAgents) { - Matcher matcher = pattern.matcher(userAgent); - if (matcher.matches()) { - return true; - } - } - return false; - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, - FilterChain chain) throws IOException, ServletException { - HttpServletRequest httpRequest = (HttpServletRequest)request; - if (!isBrowser(httpRequest.getHeader(HEADER_USER_AGENT)) || - methodsToIgnore.contains(httpRequest.getMethod()) || - httpRequest.getHeader(headerName) != null) { - chain.doFilter(request, response); - } else { - ((HttpServletResponse)response).sendError( - HttpServletResponse.SC_BAD_REQUEST, - "Missing Required Header for CSRF Vulnerability Protection"); - } - } - - @Override - public void destroy() { - } -} http://git-wip-us.apache.org/repos/asf/hadoop/blob/2e047429/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java b/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java deleted file mode 100644 index 29dccd3..0000000 --- a/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestRestCsrfPreventionFilter.java +++ /dev/null @@ -1,357 +0,0 @@ -/** - * 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.hadoop.security.http; - -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; - -import java.io.IOException; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.junit.Test; -import org.mockito.Mockito; - -public class TestRestCsrfPreventionFilter { - - private static final String NON_BROWSER = "java"; - private static final String BROWSER_AGENT = - "Mozilla/5.0 (compatible; U; ABrowse 0.6; Syllable)" + - " AppleWebKit/420+ (KHTML, like Gecko)"; - private static final String EXPECTED_MESSAGE = - "Missing Required Header for CSRF Vulnerability Protection"; - private static final String X_CUSTOM_HEADER = "X-CUSTOM_HEADER"; - - @Test - public void testNoHeaderDefaultConfig_badRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(null); - - // CSRF has not been sent - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(BROWSER_AGENT); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - verify(mockRes, atLeastOnce()).sendError( - HttpServletResponse.SC_BAD_REQUEST, EXPECTED_MESSAGE); - Mockito.verifyZeroInteractions(mockChain); - } - - @Test - public void testNoHeaderCustomAgentConfig_badRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.BROWSER_USER_AGENT_PARAM)). - thenReturn("^Mozilla.*,^Opera.*,curl"); - - // CSRF has not been sent - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn("curl"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - verify(mockRes, atLeastOnce()).sendError( - HttpServletResponse.SC_BAD_REQUEST, EXPECTED_MESSAGE); - Mockito.verifyZeroInteractions(mockChain); - } - - @Test - public void testNoHeaderDefaultConfigNonBrowser_goodRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(null); - - // CSRF has not been sent - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(NON_BROWSER); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testHeaderPresentDefaultConfig_goodRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(null); - - // CSRF HAS been sent - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn("valueUnimportant"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testHeaderPresentCustomHeaderConfig_goodRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)). - thenReturn(X_CUSTOM_HEADER); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(null); - - // 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 - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testMissingHeaderWithCustomHeaderConfig_badRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)). - thenReturn(X_CUSTOM_HEADER); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(null); - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(BROWSER_AGENT); - - // CSRF has not been sent - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verifyZeroInteractions(mockChain); - } - - @Test - public void testMissingHeaderNoMethodsToIgnoreConfig_badRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn(""); - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(BROWSER_AGENT); - - // CSRF has not been sent - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getMethod()). - thenReturn("GET"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verifyZeroInteractions(mockChain); - } - - @Test - public void testMissingHeaderIgnoreGETMethodConfig_goodRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn("GET"); - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(BROWSER_AGENT); - - // CSRF has not been sent - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getMethod()). - thenReturn("GET"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testMissingHeaderMultipleIgnoreMethodsConfig_goodRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn("GET,OPTIONS"); - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(BROWSER_AGENT); - - // CSRF has not been sent - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getMethod()). - thenReturn("OPTIONS"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testMissingHeaderMultipleIgnoreMethodsConfig_badRequest() - throws ServletException, IOException { - // Setup the configuration settings of the server - FilterConfig filterConfig = Mockito.mock(FilterConfig.class); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_HEADER_PARAM)).thenReturn(null); - Mockito.when(filterConfig.getInitParameter( - RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM)). - thenReturn("GET,OPTIONS"); - HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_USER_AGENT)). - thenReturn(BROWSER_AGENT); - - // CSRF has not been sent - Mockito.when(mockReq.getHeader(RestCsrfPreventionFilter.HEADER_DEFAULT)). - thenReturn(null); - Mockito.when(mockReq.getMethod()). - thenReturn("PUT"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class); - FilterChain mockChain = Mockito.mock(FilterChain.class); - - // Object under test - RestCsrfPreventionFilter filter = new RestCsrfPreventionFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - Mockito.verifyZeroInteractions(mockChain); - } -}
