You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by bi...@apache.org on 2011/12/01 15:00:32 UTC
svn commit: r1209084 - in /cxf/trunk:
rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/
systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/
systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/
Author: bimargulies
Date: Thu Dec 1 14:00:26 2011
New Revision: 1209084
URL: http://svn.apache.org/viewvc?rev=1209084&view=rev
Log:
CXF-3493: merge the filter beans, fix issues, add more tests. Some of them even pass.
Added:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java (contents, props changed)
- copied, changed from r1208938, cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginInputFilter.java
Removed:
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginInputFilter.java
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginOutputFilter.java
Modified:
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java
cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/beans.xml
Copied: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java (from r1208938, cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginInputFilter.java)
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java?p2=cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java&p1=cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginInputFilter.java&r1=1208938&r2=1209084&rev=1209084&view=diff
==============================================================================
--- cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginInputFilter.java (original)
+++ cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java Thu Dec 1 14:00:26 2011
@@ -26,23 +26,26 @@ import java.util.List;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
import org.apache.cxf.jaxrs.ext.RequestHandler;
+import org.apache.cxf.jaxrs.ext.ResponseHandler;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.message.Message;
/**
- * An input filter for CORS, following http://www.w3.org/TR/cors/.
- * This examines the input headers. If the request is valid, it stores
- * the information in the Exchange to allow {@link CrossOriginOutputFilter}
+ * An single class that provides both an input
+ * and an output filter for CORS, following http://www.w3.org/TR/cors/.
+ * The input examines the input headers. If the request is valid, it stores
+ * the information in the Exchange to allow the response handler
* to add the appropriate headers to the response.
*
* If you need complex or subtle control of the behavior here (e.g. clearing
* the prefight cache) you might be better off reading the source of this
* and implementing this inside your service.
*/
-public class CrossOriginInputFilter implements RequestHandler {
+public class CrossOriginResourceSharingFilter implements RequestHandler, ResponseHandler {
@Context
private HttpHeaders headers;
@@ -56,6 +59,16 @@ public class CrossOriginInputFilter impl
private List<String> allowedHeaders = Collections.emptyList();
private boolean allowAllOrigins;
+ private boolean allowCredentials;
+
+ private List<String> exposeHeaders;
+
+ private Integer maxAge;
+
+ public CrossOriginResourceSharingFilter() {
+ exposeHeaders = Collections.emptyList();
+ }
+
public Response handleRequest(Message m, ClassResourceInfo resourceClass) {
if ("OPTIONS".equals(m.get(Message.HTTP_REQUEST_METHOD))) {
OperationResourceInfo opResInfo = m.getExchange().get(OperationResourceInfo.class);
@@ -86,7 +99,7 @@ public class CrossOriginInputFilter impl
// 5.1.4 expose headers lives on the output side.
// note what kind of processing we're doing.
- m.getExchange().put(CrossOriginOutputFilter.class.getName(), "simple");
+ m.getExchange().put(CrossOriginResourceSharingFilter.class.getName(), "simple");
return null;
}
@@ -142,7 +155,7 @@ public class CrossOriginInputFilter impl
}));
// 5.2.10 add allow-headers; we pass them from here to the output filter which actually adds them.
m.getExchange().put(CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS, requestHeaders);
- m.getExchange().put(CrossOriginOutputFilter.class.getName(), "preflight");
+ m.getExchange().put(CrossOriginResourceSharingFilter.class.getName(), "preflight");
// and allow things to proceed to the output filter.
return Response.ok().build();
}
@@ -195,4 +208,106 @@ public class CrossOriginInputFilter impl
this.allowedHeaders = allowedHeaders;
}
+ public List<String> getExposeHeaders() {
+ return exposeHeaders;
+ }
+
+ public Integer getMaxAge() {
+ return maxAge;
+ }
+
+ public Response handleResponse(Message m, OperationResourceInfo ori, Response response) {
+ String op = (String)m.getExchange().get(CrossOriginResourceSharingFilter.class.getName());
+ if (op == null) {
+ return response; // we're not here.
+ }
+ List<String> originHeader = getHeadersFromInput(m, CorsHeaderConstants.HEADER_ORIGIN);
+ ResponseBuilder rbuilder = Response.fromResponse(response);
+ if ("simple".equals(op)) {
+ // 5.1.3: add Allow-Origin supplied from the input side, plus allow-credentials as requested
+ addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN, originHeader);
+ rbuilder.header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS,
+ Boolean.toString(allowCredentials));
+ // 5.1.4 add allowed headers
+ List<String> rqAllowedHeaders
+ = getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS);
+ if (rqAllowedHeaders != null) {
+ addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS, rqAllowedHeaders);
+ }
+ if (exposeHeaders.size() > 0) {
+ addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_EXPOSE_HEADERS, exposeHeaders);
+ }
+ // if someone wants to clear the cache, we can't help them.
+ return rbuilder.build();
+ } else {
+ // preflight
+ // 5.2.7 add Allow-Origin supplied from the input side, plus allow-credentials as requested
+ addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN, originHeader);
+ rbuilder.header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS,
+ Boolean.toString(allowCredentials));
+ // 5.2.8 max-age
+ if (maxAge != null) {
+ rbuilder.header(CorsHeaderConstants.HEADER_AC_MAX_AGE, maxAge.toString());
+ }
+ // 5.2.9 add allowed methods
+ /*
+ * Currently, input side just lists the one requested method, and spec endorses that.
+ */
+ addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS,
+ getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_METHODS));
+ // 5.2.10 add allowed headers
+ List<String> rqAllowedHeaders
+ = getHeadersFromInput(m, CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS);
+ if (rqAllowedHeaders != null) {
+ addHeaders(rbuilder, CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS, rqAllowedHeaders);
+ }
+ return rbuilder.build();
+
+ }
+ }
+
+ public boolean isAllowCredentials() {
+ return allowCredentials;
+ }
+
+ /**
+ * The value for the Access-Control-Allow-Credentials header. If false, no header is added.
+ * If true, the header is added with the value 'true'.
+ * @param allowCredentials
+ */
+ public void setAllowCredentials(boolean allowCredentials) {
+ this.allowCredentials = allowCredentials;
+ }
+
+ /**
+ * A list of non-simple headers to be exposed via Access-Control-Expose-Headers.
+ * @param exposeHeaders the list of (case-sensitive) header names.
+ */
+ public void setExposeHeaders(List<String> exposeHeaders) {
+ this.exposeHeaders = exposeHeaders;
+ }
+
+ /**
+ * The value for Access-Control-Max-Age.
+ * @param maxAge An integer 'delta-seconds' or null. If null, no header is added.
+ */
+ public void setMaxAge(Integer maxAge) {
+ this.maxAge = maxAge;
+ }
+
+ @SuppressWarnings("unchecked")
+ List<String> getHeadersFromInput(Message m, String key) {
+ Object obj = m.getExchange().get(key);
+ if (obj instanceof List<?>) {
+ return (List<String>)obj;
+ }
+ return null;
+ }
+
+ private void addHeaders(ResponseBuilder rb, String key, List<String> vals) {
+ for (String v : vals) {
+ rb.header(key, v);
+ }
+ }
+
}
Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/CrossOriginResourceSharingFilter.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java?rev=1209084&r1=1209083&r2=1209084&view=diff
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java (original)
+++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/BasicCrossOriginTest.java Thu Dec 1 14:00:26 2011
@@ -19,6 +19,7 @@
package org.apache.cxf.systest.jaxrs.cors;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -30,6 +31,7 @@ import org.apache.cxf.testutil.common.Ab
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
@@ -72,29 +74,110 @@ public class BasicCrossOriginTest extend
assertEquals("*", aaoHeaders[0].getValue());
}
- @Test
- public void specificOriginSimpleGet() throws Exception {
- String origin = "http://localhost:" + PORT;
-
+ private void assertAllOrigin(boolean allOrigins, String[] originList, String[] requestOrigins,
+ boolean permitted) throws ClientProtocolException, IOException {
+ if (allOrigins) {
+ originList = new String[0];
+ }
+ // tell filter what to do.
String confResult = configClient.accept("text/plain").replacePath("/setOriginList")
- .type("application/json")
- .post(new String[] {
- origin
- }, String.class);
+ .type("application/json").post(originList, String.class);
assertEquals("ok", confResult);
HttpClient httpclient = new DefaultHttpClient();
- HttpGet httpget = new HttpGet(origin + "/test/simpleGet/HelloThere");
- httpget.addHeader("Origin", origin);
+ HttpGet httpget = new HttpGet("http://localhost:" + PORT + "/test/simpleGet/HelloThere");
+ if (requestOrigins != null) {
+ for (String requestOrigin : requestOrigins) {
+ httpget.addHeader("Origin", requestOrigin);
+ }
+ }
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
String e = IOUtils.toString(entity.getContent(), "utf-8");
- assertEquals("HelloThere", e);
+ assertEquals("HelloThere", e); // ensure that we didn't bust the operation itself.
Header[] aaoHeaders = response.getHeaders(CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN);
- assertNotNull(aaoHeaders);
- assertEquals(1, aaoHeaders.length);
- assertEquals(origin, aaoHeaders[0].getValue());
+ if (permitted) {
+ assertNotNull(aaoHeaders);
+ if (allOrigins) {
+ assertEquals(1, aaoHeaders.length);
+ assertEquals("*", aaoHeaders[0].getValue());
+ } else {
+ assertEquals(requestOrigins.length, aaoHeaders.length);
+ for (int x = 0; x < requestOrigins.length; x++) {
+ assertEquals(requestOrigins[x], aaoHeaders[x].getValue());
+ }
+ }
+ } else {
+ // Origin: null? We don't use it and it's not in the CORS spec.
+ assertTrue(aaoHeaders == null || aaoHeaders.length == 0);
+ }
+ }
+
+ @Test
+ public void allowStarPassOne() throws Exception {
+ // Allow *, pass origin
+ assertAllOrigin(true, null, new String[] {
+ "http://localhost:" + PORT
+ }, true);
+ }
+
+ @Test
+ public void allowStarPassNone() throws Exception {
+ // allow *, no origin
+ assertAllOrigin(true, null, null, false);
+ }
+
+ @Test
+ public void allowOnePassOne() throws Exception {
+ // allow one, pass that one
+ assertAllOrigin(false, new String[] {
+ "http://localhost:" + PORT
+ }, new String[] {
+ "http://localhost:" + PORT
+ }, true);
+ }
+
+ @Test
+ public void allowOnePassWrong() throws Exception {
+ // allow one, pass something else
+ assertAllOrigin(false, new String[] {
+ "http://localhost:" + PORT
+ }, new String[] {
+ "http://area51.mil:31315",
+ }, false);
+ }
+
+ @Test
+ public void allowTwoPassOne() throws Exception {
+ // allow two, pass one
+ assertAllOrigin(false, new String[] {
+ "http://localhost:" + PORT, "http://area51.mil:3141"
+ }, new String[] {
+ "http://localhost:" + PORT
+ }, true);
+ }
+
+ @org.junit.Ignore
+ @Test
+ public void allowTwoPassTwo() throws Exception {
+ // allow two, pass two
+ assertAllOrigin(false, new String[] {
+ "http://localhost:" + PORT, "http://area51.mil:3141"
+ }, new String[] {
+ "http://localhost:" + PORT, "http://area51.mil:3141"
+ }, true);
+ }
+
+ @Test
+ public void allowTwoPassThree() throws Exception {
+ // allow two, pass three
+ assertAllOrigin(false, new String[] {
+ "http://localhost:" + PORT, "http://area51.mil:3141"
+ }, new String[] {
+ "http://localhost:" + PORT, "http://area51.mil:3141", "http://hogwarts.edu:9"
+ }, false);
+
}
@Ignore
Modified: cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java?rev=1209084&r1=1209083&r2=1209084&view=diff
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java (original)
+++ cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/ConfigServer.java Thu Dec 1 14:00:26 2011
@@ -26,24 +26,19 @@ import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
-import org.apache.cxf.jaxrs.cors.CrossOriginInputFilter;
-import org.springframework.beans.BeansException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
+import org.apache.cxf.jaxrs.cors.CrossOriginResourceSharingFilter;
/**
*
*/
-public class ConfigServer implements ApplicationContextAware {
- private ApplicationContext appContext;
+public class ConfigServer {
+ private CrossOriginResourceSharingFilter inputFilter;
@POST
@Consumes("application/json")
@Path("/setOriginList")
@Produces("text/plain")
public String setOriginList(String[] origins) {
- CrossOriginInputFilter inputFilter = appContext
- .getBean("cors-input", org.apache.cxf.jaxrs.cors.CrossOriginInputFilter.class);
if (origins == null || origins.length == 0) {
inputFilter.setAllowAllOrigins(true);
} else {
@@ -53,8 +48,12 @@ public class ConfigServer implements App
return "ok";
}
- public void setApplicationContext(ApplicationContext context) throws BeansException {
- appContext = context;
+ public CrossOriginResourceSharingFilter getInputFilter() {
+ return inputFilter;
+ }
+
+ public void setInputFilter(CrossOriginResourceSharingFilter inputFilter) {
+ this.inputFilter = inputFilter;
}
}
Modified: cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/beans.xml
URL: http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/beans.xml?rev=1209084&r1=1209083&r2=1209084&view=diff
==============================================================================
--- cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/beans.xml (original)
+++ cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/beans.xml Thu Dec 1 14:00:26 2011
@@ -21,19 +21,16 @@ http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd">
<import resource="classpath:/META-INF/cxf/cxf.xml" />
- <bean id="cors-input" class="org.apache.cxf.jaxrs.cors.CrossOriginInputFilter">
+ <bean id="cors-filter" class="org.apache.cxf.jaxrs.cors.CrossOriginResourceSharingFilter">
<property name="allowAllOrigins" value="true" />
</bean>
- <bean id="cors-output" class="org.apache.cxf.jaxrs.cors.CrossOriginOutputFilter">
- </bean>
<jaxrs:server id="cors-service" address="/test">
<jaxrs:serviceBeans>
<ref bean="cors-server" />
</jaxrs:serviceBeans>
<jaxrs:providers>
- <ref bean="cors-input" />
- <ref bean="cors-output" />
+ <ref bean="cors-filter" />
</jaxrs:providers><!--
<jaxrs:features>
<cxf:logging />
@@ -49,7 +46,9 @@ http://cxf.apache.org/core
</jaxrs:providers>
</jaxrs:server>
- <bean id="config-server" class="org.apache.cxf.systest.jaxrs.cors.ConfigServer"/>
+ <bean id="config-server" class="org.apache.cxf.systest.jaxrs.cors.ConfigServer">
+ <property name='inputFilter' ref='cors-filter'/>
+ </bean>
<bean id="cors-server" scope="prototype"
class="org.apache.cxf.systest.jaxrs.cors.CorsServer" />
</beans>