From 4e62f9552a2d0edede30b1d362ccad37d963de4b Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 12 Jan 2017 22:16:14 +0100 Subject: [PATCH] re implement xsrf protection for scm-manager 2.0.0 --- .../resources/AuthenticationResource.java | 5 +- .../main/java/sonia/scm/security/Xsrf.java | 45 ++++ .../scm/security/XsrfProtectionFilter.java | 140 ------------ .../scm/security/XsrfTokenClaimsEnricher.java | 90 ++++++++ ...ies.java => XsrfTokenClaimsValidator.java} | 72 +++--- .../resources/js/login/sonia.login.form.js | 30 +++ .../main/webapp/resources/js/sonia.global.js | 3 +- .../sonia/scm/security/XsrfCookiesTest.java | 108 --------- .../security/XsrfProtectionFilterTest.java | 211 ------------------ 9 files changed, 204 insertions(+), 500 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/Xsrf.java delete mode 100644 scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsEnricher.java rename scm-webapp/src/main/java/sonia/scm/security/{XsrfCookies.java => XsrfTokenClaimsValidator.java} (56%) delete mode 100644 scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java delete mode 100644 scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java index 5d559afe62..9f6ac7251e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java @@ -184,7 +184,10 @@ public class AuthenticationResource // TODO: should be configureable c.setMaxAge((int) TimeUnit.SECONDS.convert(10, TimeUnit.HOURS)); - c.setHttpOnly(true); + + // set http only flag only xsrf protection is disabled, + // because we have to extract the xsrf key with javascript in the wui + c.setHttpOnly(!configuration.isEnabledXsrfProtection()); response.addCookie(c); state = stateFactory.createState(subject); } diff --git a/scm-webapp/src/main/java/sonia/scm/security/Xsrf.java b/scm-webapp/src/main/java/sonia/scm/security/Xsrf.java new file mode 100644 index 0000000000..450783268a --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/Xsrf.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +/** + * Shared constants for Xsrf related classes. + * + * @author Sebastian Sdorra + * @since 2.0.0 + */ +public final class Xsrf { + + static final String HEADER_KEY = "X-XSRF-Token"; + + static final String CLAIMS_KEY = "scm-manager.org/xsrf"; + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java deleted file mode 100644 index 7d1a12f9f1..0000000000 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfProtectionFilter.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright (c) 2014, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ -package sonia.scm.security; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Strings; -import com.google.inject.Inject; -import java.io.IOException; -import java.util.UUID; -import javax.inject.Singleton; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import sonia.scm.Priority; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.filter.Filters; -import sonia.scm.filter.WebElement; -import sonia.scm.util.HttpUtil; -import sonia.scm.web.filter.HttpFilter; - -/** - * Xsrf protection http filter. The filter will issue an cookie with an xsrf protection token on the first ajax request - * of the scm web interface and marks the http session as xsrf protected. On every other request within a protected - * session, the web interface has to send the token from the cookie as http header on every request. If the filter - * receives an request to a protected session, without proper xsrf header the filter will abort the request and send an - * http error code back to the client. If the filter receives an request to a non protected session, from a non web - * interface client the filter will call the chain. The {@link XsrfProtectionFilter} is disabled by default and can be - * enabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}. - * - * TODO for scm-manager 2 we have to store the csrf token as part of the jwt token instead of session. - * - * @see https://bitbucket.org/sdorra/scm-manager/issues/793/json-hijacking-vulnerability-cwe-116-cwe - * @author Sebastian Sdorra - * @version 1.47 - */ -@WebElement(Filters.PATTERN_RESTAPI) -@Priority(Filters.PRIORITY_PRE_BASEURL) -public final class XsrfProtectionFilter extends HttpFilter -{ - - /** - * the logger for XsrfProtectionFilter - */ - private static final Logger logger = LoggerFactory.getLogger(XsrfProtectionFilter.class); - - /** - * Key used for session, header and cookie. - */ - static final String KEY = "X-XSRF-Token"; - - @Inject - public XsrfProtectionFilter(ScmConfiguration configuration) - { - this.configuration = configuration; - } - - @Override - protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws - IOException, ServletException - { - if (configuration.isEnabledXsrfProtection()) - { - doXsrfProtection(request, response, chain); - } - else - { - logger.trace("xsrf protection is disabled, skipping check"); - chain.doFilter(request, response); - } - } - - private void doXsrfProtection(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws - IOException, ServletException - { - HttpSession session = request.getSession(true); - String storedToken = (String) session.getAttribute(KEY); - if ( ! Strings.isNullOrEmpty(storedToken) ){ - String headerToken = request.getHeader(KEY); - if ( storedToken.equals(headerToken) ){ - logger.trace("received valid xsrf protected request"); - chain.doFilter(request, response); - } else { - // is forbidden the correct status code? - logger.warn("received request to a xsrf protected session without proper xsrf token"); - response.sendError(HttpServletResponse.SC_FORBIDDEN); - } - } else if (HttpUtil.isWUIRequest(request)) { - logger.debug("received wui request, mark session as xsrf protected and issue a new token"); - String token = createToken(); - session.setAttribute(KEY, token); - XsrfCookies.create(request, response, token); - chain.doFilter(request, response); - } else { - // handle non webinterface clients, which does not need xsrf protection - logger.trace("received request to a non xsrf protected session"); - chain.doFilter(request, response); - } - } - - private String createToken() - { - // TODO create interface and use a better method - return UUID.randomUUID().toString(); - } - - private ScmConfiguration configuration; -} diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsEnricher.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsEnricher.java new file mode 100644 index 0000000000..65b305bc74 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsEnricher.java @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2014, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ +package sonia.scm.security; + +import java.util.Map; +import java.util.UUID; +import javax.inject.Inject; +import javax.inject.Provider; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.config.ScmConfiguration; +import sonia.scm.plugin.Extension; +import sonia.scm.util.HttpUtil; + +/** + * Xsrf token claims enricher will add an xsrf protection key to the claims of the jwt token. The enricher will only + * add the xsrf protection key, if the authentication request is issued from the web interface and xsrf protection is + * enabled. The xsrf key will be validated on every request by the {@link XsrfTokenClaimsValidator}. Xsrf protection is + * disabled by default and can be enabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}. + * + * @see https://bitbucket.org/sdorra/scm-manager/issues/793/json-hijacking-vulnerability-cwe-116-cwe + * @author Sebastian Sdorra + * @since 2.0.0 + */ +@Extension +public class XsrfTokenClaimsEnricher implements TokenClaimsEnricher { + + /** + * the logger for XsrfTokenClaimsEnricher + */ + private static final Logger LOG = LoggerFactory.getLogger(XsrfTokenClaimsEnricher.class); + + private final ScmConfiguration configuration; + private final Provider requestProvider; + + @Inject + public XsrfTokenClaimsEnricher(ScmConfiguration configuration, Provider requestProvider) { + this.configuration = configuration; + this.requestProvider = requestProvider; + } + + @Override + public void enrich(Map claims) { + if (configuration.isEnabledXsrfProtection()) { + if (HttpUtil.isWUIRequest(requestProvider.get())) { + LOG.debug("received wui token claim, enrich jwt with xsrf key"); + claims.put(Xsrf.CLAIMS_KEY, createToken()); + } else { + LOG.trace("skip xsrf enrichment, because jwt session is started from a non wui client"); + } + } else { + LOG.trace("xsrf is disabled, skip xsrf enrichment"); + } + } + + private String createToken() { + // TODO create interface and use a better method + return UUID.randomUUID().toString(); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java b/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsValidator.java similarity index 56% rename from scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java rename to scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsValidator.java index de3c26995a..669d5c6dbc 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java +++ b/scm-webapp/src/main/java/sonia/scm/security/XsrfTokenClaimsValidator.java @@ -30,58 +30,52 @@ */ package sonia.scm.security; -import javax.servlet.http.Cookie; +import com.google.common.base.Strings; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Provider; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sonia.scm.plugin.Extension; /** - * Util methods to handle XsrfCookies. - * + * Validates xsrf protected token claims. The validator check if the current request contains an xsrf key which is + * equal to the token in the claims. If the claims does not contain a xsrf key, the check is passed by. The xsrf keys + * are added by the {@link XsrfTokenClaimsEnricher}. + * * @author Sebastian Sdorra - * @version 1.47 + * @since 2.0.0 */ -public final class XsrfCookies -{ +@Extension +public class XsrfTokenClaimsValidator implements TokenClaimsValidator { + + /** + * the logger for XsrfTokenClaimsEnricher + */ + private static final Logger LOG = LoggerFactory.getLogger(XsrfTokenClaimsValidator.class); + + private final Provider requestProvider; - private XsrfCookies() - { - } /** - * Creates a new xsrf protection cookie and add it to the response. + * Constructs a new instance. * - * @param request http servlet request - * @param response http servlet response - * @param token xsrf token + * @param requestProvider http request provider */ - public static void create(HttpServletRequest request, HttpServletResponse response, String token){ - applyCookie(request, response, new Cookie(XsrfProtectionFilter.KEY, token)); - + @Inject + public XsrfTokenClaimsValidator(Provider requestProvider) { + this.requestProvider = requestProvider; } - /** - * Removes the current xsrf protection cookie from response. - * - * @param request http servlet request - * @param response http servlet response - */ - public static void remove(HttpServletRequest request, HttpServletResponse response) - { - Cookie[] cookies = request.getCookies(); - if ( cookies != null ){ - for ( Cookie c : cookies ){ - if ( XsrfProtectionFilter.KEY.equals(c.getName()) ){ - c.setMaxAge(0); - c.setValue(null); - applyCookie(request, response, c); - } - } + @Override + public boolean validate(Map claims) { + String xsrfClaimValue = (String) claims.get(Xsrf.CLAIMS_KEY); + if (!Strings.isNullOrEmpty(xsrfClaimValue)) { + String xsrfHeaderValue = requestProvider.get().getHeader(Xsrf.HEADER_KEY); + return xsrfClaimValue.equals(xsrfHeaderValue); } - } - - private static void applyCookie(HttpServletRequest request, HttpServletResponse response, Cookie cookie){ - cookie.setPath(request.getContextPath()); - response.addCookie(cookie); + return true; } } diff --git a/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js b/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js index 74336ace21..fbb1fddda7 100644 --- a/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js +++ b/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js @@ -120,6 +120,7 @@ Sonia.login.Form = Ext.extend(Ext.FormPanel,{ if ( debug ){ console.debug( 'login success' ); } + this.handleXsrf(); main.loadState( action.result ); }, @@ -147,6 +148,35 @@ Sonia.login.Form = Ext.extend(Ext.FormPanel,{ } }); }, + + handleXsrf: function() { + var tokenCompressed = Ext.util.Cookies.get('X-Bearer-Token'); + if (tokenCompressed) { + var tokenClaimsCompressed = tokenCompressed.split('.')[1]; + tokenClaimsCompressed = tokenClaimsCompressed.replace('-', '+').replace('_', '/'); + if (!window.atob) { + if (debug) { + console.log('ERROR: browser does not support window.atob'); + } + } else { + var token = Ext.util.JSON.decode(window.atob(tokenClaimsCompressed)); + var xsrfToken = token['scm-manager.org/xsrf']; + if (xsrfToken) { + // TODO check for support + localStorage.setItem('X-XSRF-Token', xsrfToken); + } else { + if (debug) { + console.log('no xsrf token found'); + } + // TODO check for support + localStorage.removeItem('X-XSRF-Token'); + } + } + } else { + // TODO check for support + localStorage.removeItem('X-XSRF-Token'); + } + }, specialKeyPressed: function(field, e){ if (e.getKey() === e.ENTER) { diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.global.js b/scm-webapp/src/main/webapp/resources/js/sonia.global.js index a43feb824b..7d1b43a553 100644 --- a/scm-webapp/src/main/webapp/resources/js/sonia.global.js +++ b/scm-webapp/src/main/webapp/resources/js/sonia.global.js @@ -39,7 +39,8 @@ Ext.Ajax.defaultHeaders = { // XSRF protection Ext.Ajax.on('beforerequest', function(conn, options){ - var token = Ext.util.Cookies.get('X-XSRF-Token'); + // TODO check for support + var token = localStorage.getItem('X-XSRF-Token'); if (token){ if (!options.headers){ options.headers = {}; diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java deleted file mode 100644 index 64dd27bc3b..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/security/XsrfCookiesTest.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright (c) 2014, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - -package sonia.scm.security; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.junit.Test; -import static org.junit.Assert.*; -import org.junit.Before; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import static org.mockito.Mockito.*; -import org.mockito.runners.MockitoJUnitRunner; - -/** - * Tests for the {@link XsrfCookies} util class. - * - * @author Sebastian Sdorra - */ -@RunWith(MockitoJUnitRunner.class) -public class XsrfCookiesTest { - - @Mock - private HttpServletRequest request; - - @Mock - private HttpServletResponse response; - - /** - * Prepare mocks for testing. - */ - @Before - public void prepareMocks(){ - when(request.getContextPath()).thenReturn("/scm"); - } - - /** - * Tests create method. - */ - @Test - public void testCreate() - { - XsrfCookies.create(request, response, "mytoken"); - - // capture cookie - ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class); - verify(response).addCookie(captor.capture()); - - // check for cookie - Cookie cookie = captor.getValue(); - assertEquals(XsrfProtectionFilter.KEY, cookie.getName()); - assertEquals("/scm", cookie.getPath()); - assertEquals("mytoken", cookie.getValue()); - } - - /** - * Tests remove method. - */ - @Test - public void testRemove(){ - Cookie cookie = new Cookie(XsrfProtectionFilter.KEY, "mytoken"); - cookie.setMaxAge(15); - when(request.getCookies()).thenReturn(new Cookie[]{cookie}); - XsrfCookies.remove(request, response); - - // capture cookie - ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class); - verify(response).addCookie(captor.capture()); - - // check the captured cookie - Cookie c = captor.getValue(); - assertEquals("cookie max age should be set to 0", 0, c.getMaxAge()); - assertEquals("cookie path should be equals", cookie.getPath(), c.getPath()); - assertNull("cookie value shuld be null", c.getValue()); - } - -} \ No newline at end of file diff --git a/scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java b/scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java deleted file mode 100644 index 0a2145de04..0000000000 --- a/scm-webapp/src/test/java/sonia/scm/security/XsrfProtectionFilterTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright (c) 2014, Sebastian Sdorra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. Neither the name of SCM-Manager; nor the names of its - * contributors may be used to endorse or promote products derived from this - * software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * http://bitbucket.org/sdorra/scm-manager - * - */ - -package sonia.scm.security; - -import java.io.IOException; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import org.junit.Test; -import static org.junit.Assert.*; -import org.junit.Before; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import static org.mockito.Mockito.*; -import org.mockito.runners.MockitoJUnitRunner; -import sonia.scm.config.ScmConfiguration; -import sonia.scm.util.HttpUtil; - -/** - * Unit tests for {@link XsrfProtectionFilter}. - * - * @author Sebastian Sdorra - */ -@RunWith(MockitoJUnitRunner.class) -public class XsrfProtectionFilterTest { - - @Mock - private HttpServletRequest request; - - @Mock - private HttpServletResponse response; - - @Mock - private HttpSession session; - - @Mock - private FilterChain chain; - - private final ScmConfiguration configuration = new ScmConfiguration(); - - private final XsrfProtectionFilter filter = new XsrfProtectionFilter(configuration); - - /** - * Prepare mocks for testing. - */ - @Before - public void setUp(){ - when(request.getSession(true)).thenReturn(session); - when(request.getContextPath()).thenReturn("/scm"); - configuration.setEnabledXsrfProtection(true); - } - - /** - * Test filter method for non web interface clients. - * - * @throws IOException - * @throws ServletException - */ - @Test - public void testDoFilterFromNonWuiClient() throws IOException, ServletException - { - filter.doFilter(request, response, chain); - verify(chain).doFilter(request, response); - } - - /** - * Test filter method with disabled xsrf protection. - * - * @throws IOException - * @throws ServletException - */ - @Test - public void testDoFilterWithDisabledXsrfProtection() throws IOException, ServletException - { - // disable xsrf protection - configuration.setEnabledXsrfProtection(false); - - // set webui user-agent - when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); - - // call the filter - filter.doFilter(request, response, chain); - - // verify that no xsrf other any other cookie was set - verify(response, never()).addCookie(Mockito.any(Cookie.class)); - - // ensure filter chain is called - verify(chain).doFilter(request, response); - } - - /** - * Test filter method for first web interface request. - * - * @throws IOException - * @throws ServletException - */ - @Test - public void testDoFilterIssuesTokenOnFirstWuiRequest() throws IOException, ServletException - { - when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); - - // call the filter - filter.doFilter(request, response, chain); - - // capture cookie - ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class); - verify(response).addCookie(captor.capture()); - - // check for cookie - Cookie cookie = captor.getValue(); - assertEquals(XsrfProtectionFilter.KEY, cookie.getName()); - assertEquals("/scm", cookie.getPath()); - assertNotNull(cookie.getValue()); - - // ensure filter chain is called - verify(chain).doFilter(request, response); - } - - /** - * Test filter method on protected session with an invalid xsrf token. - * - * @throws IOException - * @throws ServletException - */ - @Test - public void testDoFilterWithInvalidToken() throws IOException, ServletException { - when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); - when(request.getHeader(XsrfProtectionFilter.KEY)).thenReturn("invalidtoken"); - when(session.getAttribute(XsrfProtectionFilter.KEY)).thenReturn("mytoken"); - - // call the filter - filter.doFilter(request, response, chain); - - // ensure response send forbidden and the chain was never called - verify(response).sendError(HttpServletResponse.SC_FORBIDDEN); - verify(chain, never()).doFilter(request, response); - } - - /** - * Test filter method on protected session without xsrf token. - * - * @throws IOException - * @throws ServletException - */ - @Test - public void testDoFilterOnProtectedSessionWithoutToken() throws IOException, ServletException { - when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); - when(session.getAttribute(XsrfProtectionFilter.KEY)).thenReturn("mytoken"); - - // call the filter - filter.doFilter(request, response, chain); - - // ensure response send forbidden and the chain was never called - verify(response).sendError(HttpServletResponse.SC_FORBIDDEN); - verify(chain, never()).doFilter(request, response); - } - - /** - * Test filter method on protected session with valid xsrf token. - * - * @throws IOException - * @throws ServletException - */ - @Test - public void testDoFilterOnProtectedSessionWithValidToken() throws IOException, ServletException { - when(request.getHeader(HttpUtil.HEADER_SCM_CLIENT)).thenReturn(HttpUtil.SCM_CLIENT_WUI); - when(request.getHeader(XsrfProtectionFilter.KEY)).thenReturn("mytoken"); - when(session.getAttribute(XsrfProtectionFilter.KEY)).thenReturn("mytoken"); - - // call the filter - filter.doFilter(request, response, chain); - - // ensure chain was called - verify(chain).doFilter(request, response); - } - -} \ No newline at end of file