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>