Author: bimargulies
Date: Fri Dec  2 17:30:42 2011
New Revision: 1209596

URL: http://svn.apache.org/viewvc?rev=1209596&view=rev
Log:
More tests, plus some substantive documentation. (CXF-3493)

Added:
    
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html
   (with props)
    
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java
   (with props)
Modified:
    
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/CrossOriginSimpleTest.java
    cxf/trunk/systests/jaxrs/src/test/resources/jaxrs_cors/WEB-INF/beans.xml

Added: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html
URL: 
http://svn.apache.org/viewvc/cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html?rev=1209596&view=auto
==============================================================================
--- 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html
 (added)
+++ 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html
 Fri Dec  2 17:30:42 2011
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML >
+<html>
+<head>
+<!--
+  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.
+-->
+</head>
+<body bgcolor="white">
+<h1>CORS</h1>
+<p>This package provides a filter to assist applications in implementing Cross 
Origin Resource Sharing, 
+as described in the <a href="http://www.w3.org/TR/cors";>CORS specification</a>.
+</p>
+<h2>CORS Access Model</h2>
+<p>
+CORS exists to protect web servers from unexpected cross-origin access. The 
premise of CORS is that many web resources 
+are deployed by people who don't want to permit cross-origin access, but who 
couldn't detect it or didn't bother
+to control it. Thus, CORS defines a set of restrictions <em>implemented on the 
client</em> that, by default, 
+prohibit cross-origin access.
+</p>
+<p>
+If you want your service to permit cross-origin access, your service must 
return additional headers to the client to reassure
+it that you really want to permit the access. {@link 
CrossOriginResourceSharingFilter} adds these headers to your service's 
+responses based on rules that you configure.
+</p>
+<h2>CORS Resource Model (versus JAX-RS)</h2>
+<p>
+CORS and JAX-RS differ, fundamentally, in how they define a resource for 
access control purposes. In CORS, a resource
+is defined by the combination of URI and HTTP method. Once a client has 
obtained access information for a URI+METHOD,
+it may cache it. JAX-RS, on the other hand, defines a resource as:
+<ul>
+<li>URI</li>
+<li>method</li>
+<li>Accepts</li>
+<li>Accepts-Language</li>
+<li>Content-Type</li>
+</ul>
+The logical place, in other words, to specify CORS policy in a JAX-RS 
application is at the level of an annotated method. However, each method is
+applied to the narrow 'resource' defined by the list above, not just the 
URI+Method pair. This will motivate the annotation model below. 
+</p>
+<h2>Simple and Preflight requests</h2>
+<p>The CORS specification differentiates two kinds of HTTP requests: 
<em>simple</em> and <em>not simple</em>. (See the specification
+for the definition.) For a simple request, the client simply
+sends the request to the service, and then looks for the 
<tt>Access-Control-</tt> headers to indicate whether the server has explicitly 
granted 
+cross-origin access. For a non-simple request, the client sends a so-called 
<em>preflight</em> request and waits for a response before 
+issuing the original request.
+<h2>Configuration via Annotation</h2>
+<p>
+One way to control the behavior of the filter is the @{@link 
CrossOriginResourceSharing} annotation on a method. 
+This is a complete solution for simple requests. You can specify all of the 
controls. However, if you have non-simple methods, the mismatch on 
+resource access models above makes it impossible for CXF to map the OPTIONS 
request that will arrive to the correct method.
+</p>
+<p>
+If all the methods of a class can share a common policy, you can attach a 
single @{@link CrossOriginResourceSharing} 
+to a resource class, and it will apply to all the resource implied by all of 
the methods.
+</p>
+<p>
+If you need finer control, you can use @{@link 
CrossOriginResourceSharingPaths} at the class level. This annotation contains a 
list of 
+@{@link CrossOriginResourceSharing} annotations. In the nested annotations, 
you add <tt>path = "/localResourcePath"</tt> to associate the 
+annotation with a path. The filter will find the annotation based on path, and 
then allow (or forbid) based on the method
+as compared to the <tt>allowedMethods</tt>.
+</p>
+<h2>Bean Configuration</h2>
+<p>
+The simplest configuration applies when you want to apply the same 
configuration to all of your resources. In this case, you can
+use the properties of {@link CrossOriginResourceSharingFilter} to specify the 
policy.
+</p>
+</body>
+</html>
\ No newline at end of file

Propchange: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
cxf/trunk/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/cors/package.html
------------------------------------------------------------------------------
    svn:mime-type = text/html

Added: 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java?rev=1209596&view=auto
==============================================================================
--- 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java
 (added)
+++ 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java
 Fri Dec  2 17:30:42 2011
@@ -0,0 +1,104 @@
+/**
+ * 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.cxf.systest.jaxrs.cors;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.OPTIONS;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.jaxrs.cors.CorsHeaderConstants;
+import org.apache.cxf.jaxrs.cors.CrossOriginResourceSharing;
+
+/**
+ * Service bean with no class-level annotation for cross-script control.
+ */
+@CrossOriginResourceSharing(allowOrigins = {
+        "http://area51.mil:31415";
+        }, allowCredentials = true, maxAge = 1, allowMethods = {
+        "PUT"
+        }, allowHeaders = {
+        "X-custom-1", "X-custom-2"
+        }, exposeHeaders = {
+        "X-custom-3", "X-custom-4"
+        }
+)
+public class AnnotatedCorsServer {
+    @Context
+    private HttpHeaders headers;
+
+    @GET
+    @Produces("text/plain")
+    @Path("/simpleGet/{echo}")
+    public String simpleGet(@PathParam("echo") String echo) {
+        return echo;
+    }
+
+    @DELETE
+    @Path("/delete")
+    public Response deleteSomething() {
+        return Response.ok().build();
+    }
+
+    @OPTIONS
+    @Path("/delete")
+    @CrossOriginResourceSharing(localPreflight = true)
+    public Response deleteOptions() {
+        String origin = headers.getRequestHeader("Origin").get(0);
+        if ("http://area51.mil:3333".equals(origin)) {
+            return 
Response.ok().header(CorsHeaderConstants.HEADER_AC_ALLOW_METHODS, "DELETE PUT")
+                .header(CorsHeaderConstants.HEADER_AC_ALLOW_CREDENTIALS, 
"false")
+                .header(CorsHeaderConstants.HEADER_AC_ALLOW_ORIGIN, 
"http://area51.mil:3333";).build();
+        } else {
+            return Response.ok().build();
+        }
+    }
+
+    @GET
+    @CrossOriginResourceSharing(allowOrigins = { "http://area51.mil:31415"; }, 
+            allowCredentials = true, 
+            exposeHeaders = { "X-custom-3", "X-custom-4" })
+    @Produces("text/plain")
+    @Path("/annotatedGet/{echo}")
+    public String annotatedGet(@PathParam("echo") String echo) {
+        return echo;
+    }
+
+    /**
+     * A method annotated to test preflight.
+     * 
+     * @param input
+     * @return
+     */
+    @PUT
+    @Consumes("text/plain")
+    @Produces("text/plain")
+    @Path("/annotatedPut")
+    public String annotatedPut(String input) {
+        return input;
+    }
+}

Propchange: 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/AnnotatedCorsServer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/CrossOriginSimpleTest.java
URL: 
http://svn.apache.org/viewvc/cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/CrossOriginSimpleTest.java?rev=1209596&r1=1209595&r2=1209596&view=diff
==============================================================================
--- 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/CrossOriginSimpleTest.java
 (original)
+++ 
cxf/trunk/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/cors/CrossOriginSimpleTest.java
 Fri Dec  2 17:30:42 2011
@@ -47,8 +47,7 @@ import org.junit.Ignore;
 import org.junit.Test;
 
 /**
- * Unit tests for simple CORS requests. Simple requests traffic only in 
allowed origins,
- * allowed credentials, and exposed headers. 
+ * Unit tests for CORS. This isn't precisely simple as it's turned out. 
  * 
  * Note that it's not the server's job to detect invalid CORS requests. If a 
client
  * fails to preflight, it's just not our job. However, also note that all 
'actual' 
@@ -304,6 +303,79 @@ public class CrossOriginSimpleTest exten
             = 
headerValues(response.getHeaders(CorsHeaderConstants.HEADER_AC_ALLOW_HEADERS));
         assertEquals(Arrays.asList(new String[] {"X-custom-1", "X-custom-2" 
}), allowHeadersValues);
     }
+    
+    @Test
+    public void testAnnotatedClassCorrectOrigin() throws Exception {
+        HttpClient httpclient = new DefaultHttpClient();
+        HttpGet httpget = new HttpGet("http://localhost:"; + PORT + 
"/antest/simpleGet/HelloThere");
+        httpget.addHeader("Origin", "http://area51.mil:31415";);
+
+        HttpResponse response = httpclient.execute(httpget);
+        assertEquals(200, response.getStatusLine().getStatusCode());
+        HttpEntity entity = response.getEntity();
+        String e = IOUtils.toString(entity.getContent(), "utf-8");
+
+        assertEquals("HelloThere", e); // ensure that we didn't bust the 
operation itself.
+        assertOriginResponse(false, new String[] {"http://area51.mil:31415"; }, 
true, response);
+    }
+    
+    @Test
+    public void testAnnotatedClassWrongOrigin() throws Exception {
+        HttpClient httpclient = new DefaultHttpClient();
+        HttpGet httpget = new HttpGet("http://localhost:"; + PORT + 
"/antest/simpleGet/HelloThere");
+        httpget.addHeader("Origin", "http://su.us:1001";);
+
+        HttpResponse response = httpclient.execute(httpget);
+        assertEquals(200, response.getStatusLine().getStatusCode());
+        HttpEntity entity = response.getEntity();
+        String e = IOUtils.toString(entity.getContent(), "utf-8");
+
+        assertEquals("HelloThere", e);
+        assertOriginResponse(false, null, false, response);
+    }
+    
+    @Test
+    public void testAnnotatedLocalPreflight() throws Exception {
+        configureAllowOrigins(true, null);
+        String r = configClient.replacePath("/setAllowCredentials/false")
+            .accept("text/plain").post(null, String.class);
+        assertEquals("ok", r);
+        
+        HttpClient httpclient = new DefaultHttpClient();
+        HttpOptions http = new HttpOptions("http://localhost:"; + PORT + 
"/antest/delete");
+        // this is the origin we expect to get.
+        http.addHeader("Origin", "http://area51.mil:3333";);
+        http.addHeader(CorsHeaderConstants.HEADER_AC_REQUEST_METHOD, "DELETE");
+        HttpResponse response = httpclient.execute(http);
+        assertEquals(200, response.getStatusLine().getStatusCode());
+        assertOriginResponse(false, new String[]{"http://area51.mil:3333"}, 
true, response);
+        assertAllowCredentials(response, false);
+        List<String> exposeHeadersValues 
+            = 
headerValues(response.getHeaders(CorsHeaderConstants.HEADER_AC_EXPOSE_HEADERS));
+        // preflight never returns Expose-Headers
+        assertEquals(Collections.emptyList(), exposeHeadersValues);
+        List<String> allowedMethods     
+            = 
headerValues(response.getHeaders(CorsHeaderConstants.HEADER_AC_ALLOW_METHODS));
+        assertEquals(Arrays.asList("DELETE PUT"), allowedMethods);
+    }
+    
+    @Test
+    public void testAnnotatedLocalPreflightNoGo() throws Exception {
+        configureAllowOrigins(true, null);
+        String r = configClient.replacePath("/setAllowCredentials/false")
+            .accept("text/plain").post(null, String.class);
+        assertEquals("ok", r);
+        
+        HttpClient httpclient = new DefaultHttpClient();
+        HttpOptions http = new HttpOptions("http://localhost:"; + PORT + 
"/antest/delete");
+        // this is the origin we expect to get.
+        http.addHeader("Origin", "http://area51.mil:4444";);
+        http.addHeader(CorsHeaderConstants.HEADER_AC_REQUEST_METHOD, "DELETE");
+        HttpResponse response = httpclient.execute(http);
+        assertEquals(200, response.getStatusLine().getStatusCode());
+        assertOriginResponse(false, new String[]{"http://area51.mil:4444"}, 
false, response);
+        // we could check that the others are also missing.
+    }
 
     @Ignore
     public static class SpringServer extends AbstractSpringServer {

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=1209596&r1=1209595&r2=1209596&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 
Fri Dec  2 17:30:42 2011
@@ -25,18 +25,29 @@ http://cxf.apache.org/core 
                <property name="allowAllOrigins" value="true" />
        </bean>
 
-       <jaxrs:server id="cors-service" address="/untest">
+       <jaxrs:server id="unann-cors-service" address="/untest">
                <jaxrs:serviceBeans>
-                       <ref bean="cors-server" />
+                       <ref bean="unann-cors-server" />
                </jaxrs:serviceBeans>
                <jaxrs:providers>
                        <ref bean="cors-filter" />
-               </jaxrs:providers><!-- 
+               </jaxrs:providers>
+               <!-- 
                <jaxrs:features>
                        <cxf:logging />
-               </jaxrs:features>-->
+               </jaxrs:features>
+               -->
+       </jaxrs:server>
 
+       <jaxrs:server id="ann-cors-service" address="/antest">
+               <jaxrs:serviceBeans>
+                       <ref bean="ann-cors-server" />
+               </jaxrs:serviceBeans>
+               <jaxrs:providers>
+                       <ref bean="cors-filter" />
+               </jaxrs:providers>
        </jaxrs:server>
+       
        <jaxrs:server id="config-service" address="/config">
                <jaxrs:serviceBeans>
                        <ref bean="config-server" />
@@ -49,6 +60,8 @@ http://cxf.apache.org/core 
        <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"
+       <bean id="unann-cors-server" scope="prototype"
                class="org.apache.cxf.systest.jaxrs.cors.UnannotatedCorsServer" 
/>
+       <bean id="ann-cors-server" scope="prototype"
+               class="org.apache.cxf.systest.jaxrs.cors.AnnotatedCorsServer" />
 </beans>


Reply via email to