blob: 64216dac85f9b9cf61e07c2cdb0d42be8b82bfb4 [file] [log] [blame]
/*
* 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.catalina.filters;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.easymock.EasyMock;
public class TestRestCsrfPreventionFilter {
private static final String NONCE = "nonce";
private static final String INVALID_NONCE = "invalid-nonce";
private static final String GET_METHOD = "GET";
private static final String POST_METHOD = "POST";
public static final String ACCEPTED_PATH1 = "/accepted/index1.jsp";
public static final String ACCEPTED_PATH2 = "/accepted/index2.jsp";
public static final String ACCEPTED_PATHS = ACCEPTED_PATH1 + "," + ACCEPTED_PATH2;
private RestCsrfPreventionFilter filter;
private TesterRequest request;
private TesterResponse response;
private TesterFilterChain filterChain;
private HttpSession session;
@Before
public void setUp() {
filter = new RestCsrfPreventionFilter() {
@Override
protected String generateNonce() {
return NONCE;
}
};
request = new TesterRequest();
response = new TesterResponse();
filterChain = new TesterFilterChain();
session = EasyMock.createMock(HttpSession.class);
}
@Test
public void testGetRequestNoSessionNoNonce() throws Exception {
setRequestExpectations(GET_METHOD, null, null);
filter.doFilter(request, response, filterChain);
verifyContinueChain();
}
@Test
public void testPostRequestNoSessionNoNonce() throws Exception {
setRequestExpectations(POST_METHOD, null, null);
filter.doFilter(request, response, filterChain);
verifyDenyResponse(HttpServletResponse.SC_FORBIDDEN);
}
@Test
public void testPostRequestSessionNoNonce1() throws Exception {
setRequestExpectations(POST_METHOD, session, null);
testPostRequestHeaderScenarios(null, true);
}
@Test
public void testPostRequestSessionNoNonce2() throws Exception {
setRequestExpectations(POST_METHOD, session, null);
testPostRequestHeaderScenarios(NONCE, true);
}
@Test
public void testPostRequestSessionInvalidNonce() throws Exception {
setRequestExpectations(POST_METHOD, session, INVALID_NONCE);
testPostRequestHeaderScenarios(NONCE, true);
}
@Test
public void testPostRequestSessionValidNonce() throws Exception {
setRequestExpectations(POST_METHOD, session, NONCE);
testPostRequestHeaderScenarios(NONCE, false);
}
@Test
public void testGetFetchRequestSessionNoNonce() throws Exception {
setRequestExpectations(GET_METHOD, session, Constants.CSRF_REST_NONCE_HEADER_FETCH_VALUE);
EasyMock.expect(session.getAttribute(Constants.CSRF_REST_NONCE_SESSION_ATTR_NAME))
.andReturn(null);
session.setAttribute(Constants.CSRF_REST_NONCE_SESSION_ATTR_NAME, NONCE);
EasyMock.expectLastCall();
EasyMock.replay(session);
filter.doFilter(request, response, filterChain);
verifyContinueChainNonceAvailable();
EasyMock.verify(session);
}
@Test
public void testPostFetchRequestSessionNoNonce() throws Exception {
setRequestExpectations(POST_METHOD, session, Constants.CSRF_REST_NONCE_HEADER_FETCH_VALUE);
testPostRequestHeaderScenarios(null, true);
}
@Test
public void testGetFetchRequestSessionNonce() throws Exception {
setRequestExpectations(GET_METHOD, session, Constants.CSRF_REST_NONCE_HEADER_FETCH_VALUE);
EasyMock.expect(session.getAttribute(Constants.CSRF_REST_NONCE_SESSION_ATTR_NAME))
.andReturn(NONCE);
EasyMock.replay(session);
filter.doFilter(request, response, filterChain);
verifyContinueChainNonceAvailable();
EasyMock.verify(session);
}
@Test
public void testPostFetchRequestSessionNonce() throws Exception {
setRequestExpectations(POST_METHOD, session, Constants.CSRF_REST_NONCE_HEADER_FETCH_VALUE);
testPostRequestHeaderScenarios(NONCE, true);
}
@Test
public void testPostRequestCustomDenyStatus() throws Exception {
setRequestExpectations(POST_METHOD, null, null);
filter.setDenyStatus(HttpServletResponse.SC_BAD_REQUEST);
filter.doFilter(request, response, filterChain);
verifyDenyResponse(HttpServletResponse.SC_BAD_REQUEST);
}
@Test
public void testPostRequestValidNonceAsParameterValidPath1() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE }, ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, false, true);
}
@Test
public void testPostRequestValidNonceAsParameterValidPath2() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE }, ACCEPTED_PATH2);
testPostRequestParamsScenarios(NONCE, false, true);
}
@Test
public void testPostRequestInvalidNonceAsParameterValidPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { INVALID_NONCE },
ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, true, true);
}
@Test
public void testPostRequestValidNonceAsParameterInvalidPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE }, ACCEPTED_PATH1
+ "blah");
testPostRequestParamsScenarios(NONCE, true, true);
}
@Test
public void testPostRequestValidNonceAsParameterNoPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE }, ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, true, false);
}
@Test
public void testPostRequestValidNonceAsParameterNoNonceInSession() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE }, ACCEPTED_PATH1);
testPostRequestParamsScenarios(null, true, true);
}
@Test
public void testPostRequestValidNonceAsParameterInvalidNonceAsHeader() throws Exception {
setRequestExpectations(POST_METHOD, session, INVALID_NONCE, new String[] { NONCE },
ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, true, true);
}
@Test
public void testPostRequestNoNonceAsParameterAndHeaderValidPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, null, ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, true, true);
}
@Test
public void testPostRequestMultipleValidNoncesAsParameterValidPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE, NONCE },
ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, false, true);
}
@Test
public void testPostRequestMultipleNoncesAsParameterValidPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { NONCE, INVALID_NONCE },
ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, true, true);
}
@Test
public void testPostRequestMultipleInvalidNoncesAsParameterValidPath() throws Exception {
setRequestExpectations(POST_METHOD, session, null, new String[] { INVALID_NONCE,
INVALID_NONCE }, ACCEPTED_PATH1);
testPostRequestParamsScenarios(NONCE, true, true);
}
@Test
public void testGETRequestFetchNonceAsParameter() throws Exception {
setRequestExpectations(GET_METHOD, null, null,
new String[] { Constants.CSRF_REST_NONCE_HEADER_FETCH_VALUE }, ACCEPTED_PATH1);
filter.setPathsAcceptingParams(ACCEPTED_PATHS);
filter.doFilter(request, response, filterChain);
verifyContinueChainNonceNotAvailable();
}
private void testPostRequestHeaderScenarios(String sessionAttr, boolean denyResponse)
throws Exception {
testPostRequestParamsScenarios(sessionAttr, denyResponse, false);
}
private void testPostRequestParamsScenarios(String sessionAttr, boolean denyResponse,
boolean configurePaths) throws Exception {
EasyMock.expect(session.getAttribute(Constants.CSRF_REST_NONCE_SESSION_ATTR_NAME))
.andReturn(sessionAttr);
EasyMock.replay(session);
if (configurePaths) {
filter.setPathsAcceptingParams(ACCEPTED_PATHS);
}
filter.doFilter(request, response, filterChain);
if (denyResponse) {
verifyDenyResponse(HttpServletResponse.SC_FORBIDDEN);
} else {
verifyContinueChain();
}
EasyMock.verify(session);
}
private void setRequestExpectations(String method, HttpSession session, String headerValue) {
setRequestExpectations(method, session, headerValue, null, null);
}
private void setRequestExpectations(String method, HttpSession session, String headerValue,
String[] paramValues, String servletPath) {
request.setMethod(method);
request.setSession(session);
request.setHeader(Constants.CSRF_REST_NONCE_HEADER_NAME, headerValue);
request.setParameterValues(paramValues);
request.setServletPath(servletPath);
}
private void verifyContinueChain() {
Assert.assertTrue(filterChain.isVisited());
}
private void verifyContinueChainNonceAvailable() {
Assert.assertTrue(NONCE.equals(response.getHeader(Constants.CSRF_REST_NONCE_HEADER_NAME)));
verifyContinueChain();
}
private void verifyContinueChainNonceNotAvailable() {
Assert.assertNull(response.getHeader(Constants.CSRF_REST_NONCE_HEADER_NAME));
verifyContinueChain();
}
private void verifyDenyResponse(int statusCode) {
Assert.assertTrue(Constants.CSRF_REST_NONCE_HEADER_REQUIRED_VALUE.equals(response
.getHeader(Constants.CSRF_REST_NONCE_HEADER_NAME)));
Assert.assertTrue(statusCode == response.getStatus());
Assert.assertTrue(!filterChain.isVisited());
}
private static class TesterFilterChain implements FilterChain {
private boolean visited = false;
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException,
ServletException {
visited = true;
}
boolean isVisited() {
return visited;
}
}
private static class TesterRequest extends TesterHttpServletRequest {
private HttpSession session;
private String[] paramValues;
private String servletPath;
void setSession(HttpSession session) {
this.session = session;
}
@Override
public HttpSession getSession(boolean create) {
return session;
}
void setParameterValues(String[] paramValues) {
this.paramValues = paramValues;
}
@Override
public String[] getParameterValues(String name) {
return paramValues;
}
void setServletPath(String servletPath) {
this.servletPath = servletPath;
}
@Override
public String getServletPath() {
return servletPath;
}
@Override
public String getPathInfo() {
return "";
}
}
private static class TesterResponse extends TesterHttpServletResponse {
@Override
public void sendError(int status, String message) throws IOException {
setStatus(status);
}
}
}