471604 - Extend CrossOriginFilter to provide a Timing-Allow-Origin header
applied from https://github.com/eclipse/jetty.project/pull/50 Also-by: David Seebacher <dseebacher@gmail.com>
This commit is contained in:
parent
04062a8383
commit
236edce34f
|
@ -65,11 +65,20 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
* https?://*.domain.[a-z]{3} that matches http or https, multiple subdomains
|
||||
* and any 3 letter top-level domain (.com, .net, .org, etc.).</dd>
|
||||
*
|
||||
* <dt>allowedTimingOrigins</dt>
|
||||
* <dd>a comma separated list of origins that are
|
||||
* allowed to time the resource. Default value is the empty string, meaning
|
||||
* no origins.
|
||||
* <p>
|
||||
* The check whether the timing header is set, will be performed only if
|
||||
* the user gets general access to the resource using the <b>allowedOrigins</b>.
|
||||
*
|
||||
* <dt>allowedMethods</dt>
|
||||
* <dd>a comma separated list of HTTP methods that
|
||||
* are allowed to be used when accessing the resources. Default value is
|
||||
* <b>GET,POST,HEAD</b></dd>
|
||||
*
|
||||
*
|
||||
* <dt>allowedHeaders</dt>
|
||||
* <dd>a comma separated list of HTTP headers that
|
||||
* are allowed to be specified when accessing the resources. Default value
|
||||
|
@ -127,8 +136,10 @@ public class CrossOriginFilter implements Filter
|
|||
public static final String ACCESS_CONTROL_MAX_AGE_HEADER = "Access-Control-Max-Age";
|
||||
public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER = "Access-Control-Allow-Credentials";
|
||||
public static final String ACCESS_CONTROL_EXPOSE_HEADERS_HEADER = "Access-Control-Expose-Headers";
|
||||
public static final String TIMING_ALLOW_ORIGIN_HEADER = "Timing-Allow-Origin";
|
||||
// Implementation constants
|
||||
public static final String ALLOWED_ORIGINS_PARAM = "allowedOrigins";
|
||||
public static final String ALLOWED_TIMING_ORIGINS_PARAM = "allowedTimingOrigins";
|
||||
public static final String ALLOWED_METHODS_PARAM = "allowedMethods";
|
||||
public static final String ALLOWED_HEADERS_PARAM = "allowedHeaders";
|
||||
public static final String PREFLIGHT_MAX_AGE_PARAM = "preflightMaxAge";
|
||||
|
@ -137,13 +148,17 @@ public class CrossOriginFilter implements Filter
|
|||
public static final String OLD_CHAIN_PREFLIGHT_PARAM = "forwardPreflight";
|
||||
public static final String CHAIN_PREFLIGHT_PARAM = "chainPreflight";
|
||||
private static final String ANY_ORIGIN = "*";
|
||||
private static final String DEFAULT_ALLOWED_ORIGINS = "*";
|
||||
private static final String DEFAULT_ALLOWED_TIMING_ORIGINS = "";
|
||||
private static final List<String> SIMPLE_HTTP_METHODS = Arrays.asList("GET", "POST", "HEAD");
|
||||
private static final List<String> DEFAULT_ALLOWED_METHODS = Arrays.asList("GET", "POST", "HEAD");
|
||||
private static final List<String> DEFAULT_ALLOWED_HEADERS = Arrays.asList("X-Requested-With", "Content-Type", "Accept", "Origin");
|
||||
|
||||
private boolean anyOriginAllowed;
|
||||
private boolean anyTimingOriginAllowed;
|
||||
private boolean anyHeadersAllowed;
|
||||
private List<String> allowedOrigins = new ArrayList<String>();
|
||||
private List<String> allowedTimingOrigins = new ArrayList<String>();
|
||||
private List<String> allowedMethods = new ArrayList<String>();
|
||||
private List<String> allowedHeaders = new ArrayList<String>();
|
||||
private List<String> exposedHeaders = new ArrayList<String>();
|
||||
|
@ -154,26 +169,10 @@ public class CrossOriginFilter implements Filter
|
|||
public void init(FilterConfig config) throws ServletException
|
||||
{
|
||||
String allowedOriginsConfig = config.getInitParameter(ALLOWED_ORIGINS_PARAM);
|
||||
if (allowedOriginsConfig == null)
|
||||
allowedOriginsConfig = "*";
|
||||
String[] allowedOrigins = StringUtil.csvSplit(allowedOriginsConfig);
|
||||
for (String allowedOrigin : allowedOrigins)
|
||||
{
|
||||
allowedOrigin = allowedOrigin.trim();
|
||||
if (allowedOrigin.length() > 0)
|
||||
{
|
||||
if (ANY_ORIGIN.equals(allowedOrigin))
|
||||
{
|
||||
anyOriginAllowed = true;
|
||||
this.allowedOrigins.clear();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.allowedOrigins.add(allowedOrigin);
|
||||
}
|
||||
}
|
||||
}
|
||||
String allowedTimingOriginsConfig = config.getInitParameter(ALLOWED_TIMING_ORIGINS_PARAM);
|
||||
|
||||
anyOriginAllowed = generateAllowedOrigins(allowedOrigins, allowedOriginsConfig, DEFAULT_ALLOWED_ORIGINS);
|
||||
anyTimingOriginAllowed = generateAllowedOrigins(allowedTimingOrigins, allowedTimingOriginsConfig, DEFAULT_ALLOWED_TIMING_ORIGINS);
|
||||
|
||||
String allowedMethodsConfig = config.getInitParameter(ALLOWED_METHODS_PARAM);
|
||||
if (allowedMethodsConfig == null)
|
||||
|
@ -224,6 +223,7 @@ public class CrossOriginFilter implements Filter
|
|||
{
|
||||
LOG.debug("Cross-origin filter configuration: " +
|
||||
ALLOWED_ORIGINS_PARAM + " = " + allowedOriginsConfig + ", " +
|
||||
ALLOWED_TIMING_ORIGINS_PARAM + " = " + allowedTimingOriginsConfig + ", " +
|
||||
ALLOWED_METHODS_PARAM + " = " + allowedMethodsConfig + ", " +
|
||||
ALLOWED_HEADERS_PARAM + " = " + allowedHeadersConfig + ", " +
|
||||
PREFLIGHT_MAX_AGE_PARAM + " = " + preflightMaxAgeConfig + ", " +
|
||||
|
@ -234,6 +234,29 @@ public class CrossOriginFilter implements Filter
|
|||
}
|
||||
}
|
||||
|
||||
private boolean generateAllowedOrigins(List<String> allowedOriginStore, String allowedOriginsConfig, String defaultOrigin)
|
||||
{
|
||||
if (allowedOriginsConfig == null)
|
||||
allowedOriginsConfig = defaultOrigin;
|
||||
String[] allowedOrigins = StringUtil.csvSplit(allowedOriginsConfig);
|
||||
for (String allowedOrigin : allowedOrigins)
|
||||
{
|
||||
if (allowedOrigin.length() > 0)
|
||||
{
|
||||
if (ANY_ORIGIN.equals(allowedOrigin))
|
||||
{
|
||||
allowedOriginStore.clear();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
allowedOriginStore.add(allowedOrigin);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||
{
|
||||
handle((HttpServletRequest)request, (HttpServletResponse)response, chain);
|
||||
|
@ -245,7 +268,7 @@ public class CrossOriginFilter implements Filter
|
|||
// Is it a cross origin request ?
|
||||
if (origin != null && isEnabled(request))
|
||||
{
|
||||
if (originMatches(origin))
|
||||
if (anyOriginAllowed || originMatches(allowedOrigins, origin))
|
||||
{
|
||||
if (isSimpleRequest(request))
|
||||
{
|
||||
|
@ -266,6 +289,15 @@ public class CrossOriginFilter implements Filter
|
|||
LOG.debug("Cross-origin request to {} is a non-simple cross-origin request", request.getRequestURI());
|
||||
handleSimpleResponse(request, response, origin);
|
||||
}
|
||||
|
||||
if (anyTimingOriginAllowed || originMatches(allowedTimingOrigins, origin))
|
||||
{
|
||||
response.setHeader(TIMING_ALLOW_ORIGIN_HEADER, origin);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Cross-origin request to " + request.getRequestURI() + " with origin " + origin + " does not match allowed timing origins " + allowedTimingOrigins);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -280,12 +312,12 @@ public class CrossOriginFilter implements Filter
|
|||
{
|
||||
// WebSocket clients such as Chrome 5 implement a version of the WebSocket
|
||||
// protocol that does not accept extra response headers on the upgrade response
|
||||
for (Enumeration connections = request.getHeaders("Connection"); connections.hasMoreElements();)
|
||||
for (Enumeration<String> connections = request.getHeaders("Connection"); connections.hasMoreElements();)
|
||||
{
|
||||
String connection = (String)connections.nextElement();
|
||||
if ("Upgrade".equalsIgnoreCase(connection))
|
||||
{
|
||||
for (Enumeration upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements();)
|
||||
for (Enumeration<String> upgrades = request.getHeaders("Upgrade"); upgrades.hasMoreElements();)
|
||||
{
|
||||
String upgrade = (String)upgrades.nextElement();
|
||||
if ("WebSocket".equalsIgnoreCase(upgrade))
|
||||
|
@ -296,11 +328,8 @@ public class CrossOriginFilter implements Filter
|
|||
return true;
|
||||
}
|
||||
|
||||
private boolean originMatches(String originList)
|
||||
private boolean originMatches(List<String> allowedOrigins, String originList)
|
||||
{
|
||||
if (anyOriginAllowed)
|
||||
return true;
|
||||
|
||||
if (originList.trim().length() == 0)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -173,7 +173,7 @@ public class CrossOriginFilterTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithMatchingOrigin() throws Exception
|
||||
public void testSimpleRequestWithMatchingOriginAndWithoutTimingOrigin() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
String origin = "http://localhost";
|
||||
|
@ -193,10 +193,66 @@ public class CrossOriginFilterTest
|
|||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.TIMING_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains("Vary"));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithMatchingOriginAndNonMatchingTimingOrigin() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
String origin = "http://localhost";
|
||||
String timingOrigin = "http://127.0.0.1";
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin);
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_TIMING_ORIGINS_PARAM, timingOrigin);
|
||||
tester.getContext().addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"Origin: " + origin + "\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertFalse(response.contains(CrossOriginFilter.TIMING_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains("Vary"));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithMatchingOriginAndMatchingTimingOrigin() throws Exception
|
||||
{
|
||||
FilterHolder filterHolder = new FilterHolder(new CrossOriginFilter());
|
||||
String origin = "http://localhost";
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, origin);
|
||||
filterHolder.setInitParameter(CrossOriginFilter.ALLOWED_TIMING_ORIGINS_PARAM, origin);
|
||||
tester.getContext().addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
tester.getContext().addServlet(new ServletHolder(new ResourceServlet(latch)), "/*");
|
||||
|
||||
String request = "" +
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"Connection: close\r\n" +
|
||||
"Origin: " + origin + "\r\n" +
|
||||
"\r\n";
|
||||
String response = tester.getResponses(request);
|
||||
Assert.assertTrue(response.contains("HTTP/1.1 200"));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS_HEADER));
|
||||
Assert.assertTrue(response.contains(CrossOriginFilter.TIMING_ALLOW_ORIGIN_HEADER));
|
||||
Assert.assertTrue(response.contains("Vary"));
|
||||
Assert.assertTrue(latch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleRequestWithMatchingMultipleOrigins() throws Exception
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue