Added CorsFilter to enable secure cross site scripting 31/1031/1
authorMadhu Venugopal <vmadhu@cisco.com>
Wed, 28 Aug 2013 05:26:13 +0000 (22:26 -0700)
committerGiovanni Meo <gmeo@cisco.com>
Wed, 28 Aug 2013 09:59:05 +0000 (11:59 +0200)
This is in addition to Ed's original Cors Filter changes. Default Cors Config
doesnt seem to work in certain scenarios. Added some custom configurations
and also added it per-bundle (started with Flow & i will add it to other bundles
once this is verified).

Also, by default AngularJS like frameworks uses HTTP OPTIONS method to check for
server options and that doesnt carry authentication headers. Hence in order for
the cors to work properly, we have to ignore authentication for OPTIONS method
alone. This is taken care in the web.xml configuration for all the northbound
bundle.

See: http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter
And: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
This is done to allow a web page using javascript to be able to make calls
to our REST APIs even though it does not originate in our domain.

Added CorsFilter bundle in Third Party to bring in the class as a Fragment
on the org.apache.catalina bundle.

Added CorsFilter to the web/root web.xml file so it will be used for
all WebApps.

Fixed the northbound integration tests

Change-Id: I29435c5820613982ef691e03a1d446bc7f958537
Signed-off-by: Madhu Venugopal <vmadhu@cisco.com>
25 files changed:
opendaylight/distribution/opendaylight/pom.xml
opendaylight/northbound/flowprogrammer/pom.xml
opendaylight/northbound/flowprogrammer/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/hosttracker/pom.xml
opendaylight/northbound/hosttracker/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/integrationtest/src/test/java/org/opendaylight/controller/northbound/integrationtest/NorthboundIT.java
opendaylight/northbound/networkconfiguration/bridgedomain/pom.xml
opendaylight/northbound/networkconfiguration/bridgedomain/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/staticrouting/pom.xml
opendaylight/northbound/staticrouting/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/statistics/pom.xml
opendaylight/northbound/statistics/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/subnets/pom.xml
opendaylight/northbound/subnets/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/switchmanager/pom.xml
opendaylight/northbound/switchmanager/src/main/resources/WEB-INF/web.xml
opendaylight/northbound/topology/pom.xml
opendaylight/northbound/topology/src/main/resources/WEB-INF/web.xml
opendaylight/samples/northbound/loadbalancer/pom.xml
opendaylight/samples/northbound/loadbalancer/src/main/resources/WEB-INF/web.xml
opendaylight/web/root/pom.xml
opendaylight/web/root/src/main/resources/WEB-INF/web.xml
third-party/org.apache.catalina.filters.CorsFilter/README [new file with mode: 0644]
third-party/org.apache.catalina.filters.CorsFilter/pom.xml [new file with mode: 0644]
third-party/org.apache.catalina.filters.CorsFilter/src/main/java/org/apache/catalina/filters/CorsFilter.java [new file with mode: 0644]

index abf508efc2bb308f2727f69d9e4d8cc9b80da35e..4dc4e6fa2e35efee89a617b014eacda9d3678fdf 100644 (file)
@@ -68,6 +68,7 @@
     <module>../../../third-party/net.sf.jung2</module>
     <module>../../../third-party/jersey-servlet</module>
     <module>../../../third-party/commons/thirdparty</module>
+    <module>../../../third-party/org.apache.catalina.filters.CorsFilter</module>
 
     <!-- SAL bundles -->
     <module>../../sal/api</module>
index 94991c573efa29267d11c6ea800af85a5b99edb6..201bf477ee210aa254e11e8de846c11d9b3aa0d0 100644 (file)
@@ -43,7 +43,8 @@
               org.opendaylight.controller.northbound.commons.utils,
               org.opendaylight.controller.sal.authorization,
               org.opendaylight.controller.usermanager,
-                          com.sun.jersey.spi.container.servlet,
+              com.sun.jersey.spi.container.servlet,
+              org.apache.catalina.filters,
               javax.ws.rs,
               javax.ws.rs.core,
               javax.xml.bind.annotation,
       <artifactId>com.sun.jersey.jersey-servlet</artifactId>
       <version>1.17-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index 4cedf2df8975bd6414fd28920dcd8db5c41b3183..5b3cec2292163b1a4ca7836dae4ea35d9e26f630 100644 (file)
           <url-pattern>/*</url-pattern>
         </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
     <security-role>
@@ -48,4 +87,4 @@
                 <auth-method>BASIC</auth-method>
                 <realm-name>opendaylight</realm-name>
         </login-config>
-</web-app>
\ No newline at end of file
+</web-app>
index 40aaf462ad8ec1a4f07a0ad36c4db51cf904cd93..b588c0715b222d74927ffd94799bdc51cd01503c 100644 (file)
@@ -51,6 +51,7 @@
               javax.xml.bind.annotation,
               javax.xml.bind,
               org.slf4j,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Web-ContextPath>/controller/nb/v2/host</Web-ContextPath>
       <artifactId>enunciate-core-annotations</artifactId>
       <version>${enunciate.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index 0fa8b95dd00368600f7cfcc26196b4eca07adda0..01b8fedce125301acaf2bc3b1c8adf2bbea9d3fa 100644 (file)
     <url-pattern>/*</url-pattern>
   </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index 6958ecb96ad77e7da668d62d0f0871b6158c5e60..85b77b2f4de000f37b2ebe487623a6aa5fc0df8a 100644 (file)
@@ -1366,6 +1366,8 @@ public class NorthboundIT {
                 mavenBundle("org.ow2.chameleon.management", "chameleon-mbeans", "1.0.0"),
                 mavenBundle("org.opendaylight.controller.thirdparty", "net.sf.jung2", "2.0.1-SNAPSHOT"),
                 mavenBundle("org.opendaylight.controller.thirdparty", "com.sun.jersey.jersey-servlet", "1.17-SNAPSHOT"),
+                mavenBundle("org.opendaylight.controller.thirdparty", "org.apache.catalina.filters.CorsFilter",
+                            "7.0.42-SNAPSHOT").noStart(),
 
                 // Jersey needs to be started before the northbound application
                 // bundles, using a lower start level
index cba14fdc78e92aeb1da39c5fc947a335ce4d26ec..8cb13200431c1a49d82727aba07025deb332f602 100644 (file)
@@ -52,6 +52,7 @@
               javax.ws.rs.core,
               javax.xml.bind.annotation,
               javax.xml.bind,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Export-Package>
       <artifactId>com.sun.jersey.jersey-servlet</artifactId>
       <version>1.17-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index b7f35c3f96d97bfa142bd163f94aaa9781b6b8d9..f4de222acc0f33140565c98dec2420ff2f11c9a4 100644 (file)
                 <url-pattern>/*</url-pattern>
         </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>BridgeDomain Configuration NorthBound API</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index bb88465907191dd2bc2fea0d7f7061190a3d1aee..fa9341f681bd62d9941dfc8d581d7a2f9091af85 100644 (file)
@@ -50,6 +50,7 @@
               javax.ws.rs.core,
               javax.xml.bind.annotation,
               javax.xml.bind,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Export-Package>
       <artifactId>com.sun.jersey.jersey-servlet</artifactId>
       <version>1.17-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index 4a040c1a1f861e3f38e0bf762cb5170832710b19..0bf186b50e0c6616504c896219252d0f6ef4d52c 100644 (file)
   </servlet>
 
         <servlet-mapping>
-                <servlet-name>JAXRSStaticRouting</servlet-name>
-                <url-pattern>/*</url-pattern>
+          <servlet-name>JAXRSStaticRouting</servlet-name>
+          <url-pattern>/*</url-pattern>
         </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index cad50e299834a633a03b36e95776e93120bd9abe..db4c4a941342a23f8cb34c58c9d0f6c7f1e73b7f 100644 (file)
@@ -58,6 +58,7 @@
                 javax.xml.bind.annotation,
                 javax.xml.bind,
                 org.slf4j,
+                org.apache.catalina.filters,
                 !org.codehaus.enunciate.jaxrs
               </Import-Package>
             <Export-Package>
       <artifactId>enunciate-core-annotations</artifactId>
       <version>${enunciate.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index f152aa75a2b1af7321ccf33d9776563750e82967..db0460ba56db2c4b2541cbecaa94d45df3fe4158 100644 (file)
     <url-pattern>/*</url-pattern>
   </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index a3931beadd34540de0c3d97cdd84e83ffe6428cd..43b8f9ebb08bb87acbf84639be432352417b8df4 100644 (file)
@@ -65,6 +65,7 @@
               javax.xml.bind,
               javax.xml.bind.annotation,
               org.slf4j,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Export-Package>
       <artifactId>enunciate-core-annotations</artifactId>
       <version>${enunciate.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index f7eccef6666f7d2ec8f0dc2d8b36215e0c9801a1..a5c70ee9d82bc232a9b0578a88bd76f37973fb5f 100644 (file)
     <servlet-name>JAXRSSubnets</servlet-name>
     <url-pattern>/*</url-pattern>
   </servlet-mapping>
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index 556c964055b2c335d80f57e90839b0adabf48a62..dd7ff9a75b45bf2fe1b42ec2d68e3a99ffe8e2fa 100644 (file)
@@ -51,6 +51,7 @@
               javax.xml.bind.annotation,
               javax.xml.bind,
               org.slf4j,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Web-ContextPath>/controller/nb/v2/switch</Web-ContextPath>
       <artifactId>enunciate-core-annotations</artifactId>
       <version>${enunciate.version}</version>
     </dependency>
-
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index 188b21b24d000014c1f498f6f4e101ed8dd25c21..ea6fcc99b2ab475cec28d4d93ee4d778bd36b1ea 100644 (file)
     <url-pattern>/*</url-pattern>
   </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index 726f7975f0f0f8c9441156cbf8a98886f9faf052..007bdbebdcf053e3365e1462732257ac05b8b7b9 100644 (file)
@@ -54,6 +54,7 @@
               javax.xml.bind,
               javax.xml.bind.annotation,
               org.slf4j,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Web-ContextPath>/controller/nb/v2/topology</Web-ContextPath>
       <artifactId>com.sun.jersey.jersey-servlet</artifactId>
       <version>1.17-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index a46e433054831408b9907b33f7d95443e1cf104e..bc818c8c6d46e1a3d86d46f162a9bd628e51e8a5 100644 (file)
     <url-pattern>/*</url-pattern>
   </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index dc23940d55ae6cf4ef30a96ac0b35dd2bdab1bb8..ed078a0551e28f49e5f7747de9f52f892155709d 100644 (file)
@@ -51,6 +51,7 @@
               javax.xml.bind.annotation,
               javax.xml.bind,
               org.slf4j,
+              org.apache.catalina.filters,
               !org.codehaus.enunciate.jaxrs
             </Import-Package>
             <Web-ContextPath>/one/nb/v2/lb</Web-ContextPath>
       <artifactId>enunciate-core-annotations</artifactId>
       <version>${enunciate.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index aac4647de669a930dbd0ffa39d30130434946054..0e8f8c1b5646b7eafcb223a7c4d5dd009cda52df 100644 (file)
     <url-pattern>/*</url-pattern>
   </servlet-mapping>
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>NB api</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+          <web-resource-collection>
+            <web-resource-name>NB api</web-resource-name>
+            <url-pattern>/*</url-pattern>
+            <http-method>POST</http-method>
+            <http-method>GET</http-method>
+            <http-method>PUT</http-method>
+            <http-method>PATCH</http-method>
+            <http-method>DELETE</http-method>
+            <http-method>HEAD</http-method>
+          </web-resource-collection>
+          <auth-constraint>
+            <role-name>System-Admin</role-name>
+            <role-name>Network-Admin</role-name>
+            <role-name>Network-Operator</role-name>
+            <role-name>Container-User</role-name>
+          </auth-constraint>
         </security-constraint>
 
         <security-role>
index ed899c5b5c7e50e74581b13b5f8988fd78a18a69..809751d443e47f74819964df5e4b287248eaaba9 100644 (file)
@@ -73,7 +73,8 @@
               org.springframework.web.servlet.view.json,
               org.springframework.web.filter,
               org.springframework.web.context,
-              org.springframework.util
+              org.springframework.util,
+              org.apache.catalina.filters
             </Import-Package>
             <Export-Package>
               org.opendaylight.controller.web
       <version>3.2.1.RELEASE</version>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller.thirdparty</groupId>
+      <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+      <version>7.0.42-SNAPSHOT</version>
+    </dependency>
   </dependencies>
 </project>
index 557b9c74f5a0b3fd28cb53281339105c242d9698..0c5cb3ac2719341726cb1235c2c714490feafdf5 100644 (file)
@@ -6,28 +6,66 @@
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
 
+        <filter>
+          <filter-name>CorsFilter</filter-name>
+          <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+          <init-param>
+            <param-name>cors.allowed.origins</param-name>
+            <param-value>*</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.methods</param-name>
+            <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.allowed.headers</param-name>
+            <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.exposed.headers</param-name>
+            <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.support.credentials</param-name>
+            <param-value>true</param-value>
+          </init-param>
+          <init-param>
+            <param-name>cors.preflight.maxage</param-name>
+            <param-value>10</param-value>
+          </init-param>
+        </filter>
+        <filter-mapping>
+          <filter-name>CorsFilter</filter-name>
+          <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
         <security-constraint>
-                <web-resource-collection>
-                        <web-resource-name>free access</web-resource-name>
-                        <url-pattern>/js/*</url-pattern>
-                        <url-pattern>/images/*</url-pattern>
-                        <url-pattern>/css/*</url-pattern>
-                        <url-pattern>/favicon.ico</url-pattern>
-                </web-resource-collection>
+          <web-resource-collection>
+             <web-resource-name>free access</web-resource-name>
+             <url-pattern>/js/*</url-pattern>
+             <url-pattern>/images/*</url-pattern>
+             <url-pattern>/css/*</url-pattern>
+             <url-pattern>/favicon.ico</url-pattern>
+          </web-resource-collection>
         </security-constraint>
 
         <security-constraint>
-                <display-name>RootApp</display-name>
-                <web-resource-collection>
-                        <web-resource-name>RootGUI</web-resource-name>
-                        <url-pattern>/*</url-pattern>
-                </web-resource-collection>
-                <auth-constraint>
-                        <role-name>System-Admin</role-name>
-                        <role-name>Network-Admin</role-name>
-                        <role-name>Network-Operator</role-name>
-                        <role-name>Container-User</role-name>
-                </auth-constraint>
+           <display-name>RootApp</display-name>
+           <web-resource-collection>
+              <web-resource-name>RootGUI</web-resource-name>
+              <url-pattern>/*</url-pattern>
+              <http-method>POST</http-method>
+              <http-method>GET</http-method>
+              <http-method>PUT</http-method>
+              <http-method>DELETE</http-method>
+              <http-method>HEAD</http-method>
+           </web-resource-collection>
+           <auth-constraint>
+               <role-name>System-Admin</role-name>
+               <role-name>Network-Admin</role-name>
+               <role-name>Network-Operator</role-name>
+               <role-name>Container-User</role-name>
+           </auth-constraint>
         </security-constraint>
 
         <security-role>
diff --git a/third-party/org.apache.catalina.filters.CorsFilter/README b/third-party/org.apache.catalina.filters.CorsFilter/README
new file mode 100644 (file)
index 0000000..e2d22ab
--- /dev/null
@@ -0,0 +1,12 @@
+See: http://tomcat.apache.org/tomcat-7.0-doc/config/filter.html#CORS_Filter
+And: http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
+This is done to allow a web page using javascript to be able to make calls
+to our REST APIs even though it does not originate in our domain.
+
+This bundle just rolls up org.apache.catalina.filters.CorsFilter and adds it as a 
+fragment to the org.apache.catalina bundle.
+
+The reason this is necessary is because the CorsFilter class was originally added
+at Tomcat 7.0.42, and we are using 7.0.32.  As the CorsFilter class is a simple one,
+with very few dependencies, this seemed the best way to bring it in.
+
diff --git a/third-party/org.apache.catalina.filters.CorsFilter/pom.xml b/third-party/org.apache.catalina.filters.CorsFilter/pom.xml
new file mode 100644 (file)
index 0000000..0789d50
--- /dev/null
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <!-- Get some common settings for the project we are using it in -->
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>commons.thirdparty</artifactId>
+    <version>1.1.0-SNAPSHOT</version>
+    <relativePath>../commons/thirdparty</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.opendaylight.controller.thirdparty</groupId>
+  <artifactId>org.apache.catalina.filters.CorsFilter</artifactId>
+  <version>7.0.42-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.6</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Fragment-Host>
+              org.apache.catalina
+            </Fragment-Host>
+            <Import-Package>
+              javax.servlet,
+              javax.servlet.http,
+              org.apache.catalina.filters,
+              org.apache.juli.logging,
+              org.apache.tomcat.util.res,
+              org.apache.catalina.comet,
+              org.apache.tomcat.util
+            </Import-Package>
+          </instructions>
+          <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>equinoxSDK381</groupId>
+      <artifactId>javax.servlet</artifactId>
+      <version>3.0.0.v201112011016</version>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.juli.extras</artifactId>
+      <version>7.0.32.v201211081135</version>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.tomcat.util</artifactId>
+      <version>7.0.32.v201211201952</version>
+    </dependency>
+    <dependency>
+      <groupId>orbit</groupId>
+      <artifactId>org.apache.catalina</artifactId>
+      <version>7.0.32.v201211201336</version>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/third-party/org.apache.catalina.filters.CorsFilter/src/main/java/org/apache/catalina/filters/CorsFilter.java b/third-party/org.apache.catalina.filters.CorsFilter/src/main/java/org/apache/catalina/filters/CorsFilter.java
new file mode 100644 (file)
index 0000000..8069c99
--- /dev/null
@@ -0,0 +1,1166 @@
+/*
+ * 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 java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.filters.Constants;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * <p>
+ * A {@link Filter} that enable client-side cross-origin requests by
+ * implementing W3C's CORS (<b>C</b>ross-<b>O</b>rigin <b>R</b>esource
+ * <b>S</b>haring) specification for resources. Each {@link HttpServletRequest}
+ * request is inspected as per specification, and appropriate response headers
+ * are added to {@link HttpServletResponse}.
+ * </p>
+ *
+ * <p>
+ * By default, it also sets following request attributes, that help to
+ * determine the nature of the request downstream.
+ * <ul>
+ * <li><b>cors.isCorsRequest:</b> Flag to determine if the request is a CORS
+ * request. Set to <code>true</code> if a CORS request; <code>false</code>
+ * otherwise.</li>
+ * <li><b>cors.request.origin:</b> The Origin URL, i.e. the URL of the page from
+ * where the request is originated.</li>
+ * <li>
+ * <b>cors.request.type:</b> Type of request. Possible values:
+ * <ul>
+ * <li>SIMPLE: A request which is not preceded by a pre-flight request.</li>
+ * <li>ACTUAL: A request which is preceded by a pre-flight request.</li>
+ * <li>PRE_FLIGHT: A pre-flight request.</li>
+ * <li>NOT_CORS: A normal same-origin request.</li>
+ * <li>INVALID_CORS: A cross-origin request which is invalid.</li>
+ * </ul>
+ * </li>
+ * <li><b>cors.request.headers:</b> Request headers sent as
+ * 'Access-Control-Request-Headers' header, for pre-flight request.</li>
+ * </ul>
+ * </p>
+ *
+ * @see <a href="http://www.w3.org/TR/cors/">CORS specification</a>
+ *
+ */
+public final class CorsFilter implements Filter {
+
+    private static final Log log = LogFactory.getLog(CorsFilter.class);
+
+    private static final StringManager sm =
+            StringManager.getManager(Constants.Package);
+
+
+    /**
+     * A {@link Collection} of origins consisting of zero or more origins that
+     * are allowed access to the resource.
+     */
+    private final Collection<String> allowedOrigins;
+
+    /**
+     * Determines if any origin is allowed to make request.
+     */
+    private boolean anyOriginAllowed;
+
+    /**
+     * A {@link Collection} of methods consisting of zero or more methods that
+     * are supported by the resource.
+     */
+    private final Collection<String> allowedHttpMethods;
+
+    /**
+     * A {@link Collection} of headers consisting of zero or more header field
+     * names that are supported by the resource.
+     */
+    private final Collection<String> allowedHttpHeaders;
+
+    /**
+     * A {@link Collection} of exposed headers consisting of zero or more header
+     * field names of headers other than the simple response headers that the
+     * resource might use and can be exposed.
+     */
+    private final Collection<String> exposedHeaders;
+
+    /**
+     * A supports credentials flag that indicates whether the resource supports
+     * user credentials in the request. It is true when the resource does and
+     * false otherwise.
+     */
+    private boolean supportsCredentials;
+
+    /**
+     * Indicates (in seconds) how long the results of a pre-flight request can
+     * be cached in a pre-flight result cache.
+     */
+    private long preflightMaxAge;
+
+    /**
+     * Determines if the request should be decorated or not.
+     */
+    private boolean decorateRequest;
+
+
+    public CorsFilter() {
+        this.allowedOrigins = new HashSet<String>();
+        this.allowedHttpMethods = new HashSet<String>();
+        this.allowedHttpHeaders = new HashSet<String>();
+        this.exposedHeaders = new HashSet<String>();
+    }
+
+
+    @Override
+    public void doFilter(final ServletRequest servletRequest,
+            final ServletResponse servletResponse, final FilterChain filterChain)
+            throws IOException, ServletException {
+        if (!(servletRequest instanceof HttpServletRequest) ||
+                !(servletResponse instanceof HttpServletResponse)) {
+            throw new ServletException(sm.getString("corsFilter.onlyHttp"));
+        }
+
+        // Safe to downcast at this point.
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+        // Determines the CORS request type.
+        CorsFilter.CORSRequestType requestType = checkRequestType(request);
+
+        // Adds CORS specific attributes to request.
+        if (decorateRequest) {
+            CorsFilter.decorateCORSProperties(request, requestType);
+        }
+        switch (requestType) {
+        case SIMPLE:
+            // Handles a Simple CORS request.
+            this.handleSimpleCORS(request, response, filterChain);
+            break;
+        case ACTUAL:
+            // Handles an Actual CORS request.
+            this.handleSimpleCORS(request, response, filterChain);
+            break;
+        case PRE_FLIGHT:
+            // Handles a Pre-flight CORS request.
+            this.handlePreflightCORS(request, response, filterChain);
+            break;
+        case NOT_CORS:
+            // Handles a Normal request that is not a cross-origin request.
+            this.handleNonCORS(request, response, filterChain);
+            break;
+        default:
+            // Handles a CORS request that violates specification.
+            this.handleInvalidCORS(request, response, filterChain);
+            break;
+        }
+    }
+
+
+    @Override
+    public void init(final FilterConfig filterConfig) throws ServletException {
+        // Initialize defaults
+        parseAndStore(DEFAULT_ALLOWED_ORIGINS, DEFAULT_ALLOWED_HTTP_METHODS,
+                DEFAULT_ALLOWED_HTTP_HEADERS, DEFAULT_EXPOSED_HEADERS,
+                DEFAULT_SUPPORTS_CREDENTIALS, DEFAULT_PREFLIGHT_MAXAGE,
+                DEFAULT_DECORATE_REQUEST);
+
+        if (filterConfig != null) {
+            String configAllowedOrigins = filterConfig
+                    .getInitParameter(PARAM_CORS_ALLOWED_ORIGINS);
+            String configAllowedHttpMethods = filterConfig
+                    .getInitParameter(PARAM_CORS_ALLOWED_METHODS);
+            String configAllowedHttpHeaders = filterConfig
+                    .getInitParameter(PARAM_CORS_ALLOWED_HEADERS);
+            String configExposedHeaders = filterConfig
+                    .getInitParameter(PARAM_CORS_EXPOSED_HEADERS);
+            String configSupportsCredentials = filterConfig
+                    .getInitParameter(PARAM_CORS_SUPPORT_CREDENTIALS);
+            String configPreflightMaxAge = filterConfig
+                    .getInitParameter(PARAM_CORS_PREFLIGHT_MAXAGE);
+            String configDecorateRequest = filterConfig
+                    .getInitParameter(PARAM_CORS_REQUEST_DECORATE);
+
+            parseAndStore(configAllowedOrigins, configAllowedHttpMethods,
+                    configAllowedHttpHeaders, configExposedHeaders,
+                    configSupportsCredentials, configPreflightMaxAge,
+                    configDecorateRequest);
+        }
+    }
+
+
+    /**
+     * Handles a CORS request of type {@link CORSRequestType}.SIMPLE.
+     *
+     * @param request
+     *            The {@link HttpServletRequest} object.
+     * @param response
+     *            The {@link HttpServletResponse} object.
+     * @param filterChain
+     *            The {@link FilterChain} object.
+     * @throws IOException
+     * @throws ServletException
+     * @see <a href="http://www.w3.org/TR/cors/#resource-requests">Simple
+     *      Cross-Origin Request, Actual Request, and Redirects</a>
+     */
+    protected void handleSimpleCORS(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain filterChain)
+            throws IOException, ServletException {
+
+        CorsFilter.CORSRequestType requestType = checkRequestType(request);
+        if (!(requestType == CorsFilter.CORSRequestType.SIMPLE ||
+                requestType == CorsFilter.CORSRequestType.ACTUAL)) {
+            throw new IllegalArgumentException(
+                    sm.getString("corsFilter.wrongType2",
+                            CorsFilter.CORSRequestType.SIMPLE,
+                            CorsFilter.CORSRequestType.ACTUAL));
+        }
+
+        final String origin = request
+                .getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
+        final String method = request.getMethod();
+
+        // Section 6.1.2
+        if (!isOriginAllowed(origin)) {
+            handleInvalidCORS(request, response, filterChain);
+            return;
+        }
+
+        if (!allowedHttpMethods.contains(method)) {
+            handleInvalidCORS(request, response, filterChain);
+            return;
+        }
+
+        // Section 6.1.3
+        // Add a single Access-Control-Allow-Origin header.
+        if (anyOriginAllowed && !supportsCredentials) {
+            // If resource doesn't support credentials and if any origin is
+            // allowed
+            // to make CORS request, return header with '*'.
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+                    "*");
+        } else {
+            // If the resource supports credentials add a single
+            // Access-Control-Allow-Origin header, with the value of the Origin
+            // header as value.
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+                    origin);
+        }
+
+        // Section 6.1.3
+        // If the resource supports credentials, add a single
+        // Access-Control-Allow-Credentials header with the case-sensitive
+        // string "true" as value.
+        if (supportsCredentials) {
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
+                    "true");
+        }
+
+        // Section 6.1.4
+        // If the list of exposed headers is not empty add one or more
+        // Access-Control-Expose-Headers headers, with as values the header
+        // field names given in the list of exposed headers.
+        if ((exposedHeaders != null) && (exposedHeaders.size() > 0)) {
+            String exposedHeadersString = join(exposedHeaders, ",");
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
+                    exposedHeadersString);
+        }
+
+        // Forward the request down the filter chain.
+        filterChain.doFilter(request, response);
+    }
+
+
+    /**
+     * Handles CORS pre-flight request.
+     *
+     * @param request
+     *            The {@link HttpServletRequest} object.
+     * @param response
+     *            The {@link HttpServletResponse} object.
+     * @param filterChain
+     *            The {@link FilterChain} object.
+     * @throws IOException
+     * @throws ServletException
+     */
+    protected void handlePreflightCORS(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain filterChain)
+            throws IOException, ServletException {
+
+        CORSRequestType requestType = checkRequestType(request);
+        if (requestType != CORSRequestType.PRE_FLIGHT) {
+            throw new IllegalArgumentException(
+                    sm.getString("corsFilter.wrongType1",
+                            CORSRequestType.PRE_FLIGHT.name().toLowerCase()));
+        }
+
+        final String origin = request
+                .getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
+
+        // Section 6.2.2
+        if (!isOriginAllowed(origin)) {
+            handleInvalidCORS(request, response, filterChain);
+            return;
+        }
+
+        // Section 6.2.3
+        String accessControlRequestMethod = request.getHeader(
+                CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
+        if (accessControlRequestMethod == null ||
+                !HTTP_METHODS.contains(accessControlRequestMethod.trim())) {
+            handleInvalidCORS(request, response, filterChain);
+            return;
+        } else {
+            accessControlRequestMethod = accessControlRequestMethod.trim();
+        }
+
+        // Section 6.2.4
+        String accessControlRequestHeadersHeader = request.getHeader(
+                CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
+        List<String> accessControlRequestHeaders = new LinkedList<String>();
+        if (accessControlRequestHeadersHeader != null &&
+                !accessControlRequestHeadersHeader.trim().isEmpty()) {
+            String[] headers = accessControlRequestHeadersHeader.trim().split(
+                    ",");
+            for (String header : headers) {
+                accessControlRequestHeaders.add(header.trim().toLowerCase());
+            }
+        }
+
+        // Section 6.2.5
+        if (!allowedHttpMethods.contains(accessControlRequestMethod)) {
+            handleInvalidCORS(request, response, filterChain);
+            return;
+        }
+
+        // Section 6.2.6
+        if (!accessControlRequestHeaders.isEmpty()) {
+            for (String header : accessControlRequestHeaders) {
+                if (!allowedHttpHeaders.contains(header)) {
+                    handleInvalidCORS(request, response, filterChain);
+                    return;
+                }
+            }
+        }
+
+        // Section 6.2.7
+        if (supportsCredentials) {
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+                    origin);
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
+                    "true");
+        } else {
+            if (anyOriginAllowed) {
+                response.addHeader(
+                        CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+                        "*");
+            } else {
+                response.addHeader(
+                        CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
+                        origin);
+            }
+        }
+
+        // Section 6.2.8
+        if (preflightMaxAge > 0) {
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE,
+                    String.valueOf(preflightMaxAge));
+        }
+
+        // Section 6.2.9
+        response.addHeader(
+                CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
+                accessControlRequestMethod);
+
+        // Section 6.2.10
+        if ((allowedHttpHeaders != null) && (!allowedHttpHeaders.isEmpty())) {
+            response.addHeader(
+                    CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
+                    join(allowedHttpHeaders, ","));
+        }
+
+        // Do not forward the request down the filter chain.
+    }
+
+
+    /**
+     * Handles a request, that's not a CORS request, but is a valid request i.e.
+     * it is not a cross-origin request. This implementation, just forwards the
+     * request down the filter chain.
+     *
+     * @param request
+     *            The {@link HttpServletRequest} object.
+     * @param response
+     *            The {@link HttpServletResponse} object.
+     * @param filterChain
+     *            The {@link FilterChain} object.
+     * @throws IOException
+     * @throws ServletException
+     */
+    private void handleNonCORS(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain filterChain)
+            throws IOException, ServletException {
+        // Let request pass.
+        filterChain.doFilter(request, response);
+    }
+
+
+    /**
+     * Handles a CORS request that violates specification.
+     *
+     * @param request
+     *            The {@link HttpServletRequest} object.
+     * @param response
+     *            The {@link HttpServletResponse} object.
+     * @param filterChain
+     *            The {@link FilterChain} object.
+     */
+    private void handleInvalidCORS(final HttpServletRequest request,
+            final HttpServletResponse response, final FilterChain filterChain) {
+        String origin = request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN);
+        String method = request.getMethod();
+        String accessControlRequestHeaders = request.getHeader(
+                REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
+
+        response.setContentType("text/plain");
+        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+        response.resetBuffer();
+
+        if (log.isDebugEnabled()) {
+            // Debug so no need for i18n
+            StringBuilder message =
+                    new StringBuilder("Invalid CORS request; Origin=");
+            message.append(origin);
+            message.append(";Method=");
+            message.append(method);
+            if (accessControlRequestHeaders != null) {
+                message.append(";Access-Control-Request-Headers=");
+                message.append(accessControlRequestHeaders);
+            }
+            log.debug(message.toString());
+        }
+    }
+
+
+    @Override
+    public void destroy() {
+        // NOOP
+    }
+
+
+    /**
+     * Decorates the {@link HttpServletRequest}, with CORS attributes.
+     * <ul>
+     * <li><b>cors.isCorsRequest:</b> Flag to determine if request is a CORS
+     * request. Set to <code>true</code> if CORS request; <code>false</code>
+     * otherwise.</li>
+     * <li><b>cors.request.origin:</b> The Origin URL.</li>
+     * <li><b>cors.request.type:</b> Type of request. Values:
+     * <code>simple</code> or <code>preflight</code> or <code>not_cors</code> or
+     * <code>invalid_cors</code></li>
+     * <li><b>cors.request.headers:</b> Request headers sent as
+     * 'Access-Control-Request-Headers' header, for pre-flight request.</li>
+     * </ul>
+     *
+     * @param request
+     *            The {@link HttpServletRequest} object.
+     * @param corsRequestType
+     *            The {@link CORSRequestType} object.
+     */
+    protected static void decorateCORSProperties(
+            final HttpServletRequest request,
+            final CORSRequestType corsRequestType) {
+        if (request == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("corsFilter.nullRequest"));
+        }
+
+        if (corsRequestType == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("corsFilter.nullRequestType"));
+        }
+
+        switch (corsRequestType) {
+        case SIMPLE:
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+                    Boolean.TRUE);
+            request.setAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
+                    request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN));
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
+                    corsRequestType.name().toLowerCase());
+            break;
+        case ACTUAL:
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+                    Boolean.TRUE);
+            request.setAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
+                    request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN));
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
+                    corsRequestType.name().toLowerCase());
+            break;
+        case PRE_FLIGHT:
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+                    Boolean.TRUE);
+            request.setAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN,
+                    request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN));
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE,
+                    corsRequestType.name().toLowerCase());
+            String headers = request.getHeader(
+                    REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS);
+            if (headers == null) {
+                headers = "";
+            }
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS, headers);
+            break;
+        case NOT_CORS:
+            request.setAttribute(
+                    CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST,
+                    Boolean.FALSE);
+            break;
+        default:
+            // Don't set any attributes
+            break;
+        }
+    }
+
+
+    /**
+     * Joins elements of {@link Set} into a string, where each element is
+     * separated by the provided separator.
+     *
+     * @param elements
+     *            The {@link Set} containing elements to join together.
+     * @param joinSeparator
+     *            The character to be used for separating elements.
+     * @return The joined {@link String}; <code>null</code> if elements
+     *         {@link Set} is null.
+     */
+    protected static String join(final Collection<String> elements,
+            final String joinSeparator) {
+        String separator = ",";
+        if (elements == null) {
+            return null;
+        }
+        if (joinSeparator != null) {
+            separator = joinSeparator;
+        }
+        StringBuilder buffer = new StringBuilder();
+        boolean isFirst = true;
+        for (String element : elements) {
+            if (!isFirst) {
+                buffer.append(separator);
+            } else {
+                isFirst = false;
+            }
+
+            if (element != null) {
+                buffer.append(element);
+            }
+        }
+
+        return buffer.toString();
+    }
+
+
+    /**
+     * Determines the request type.
+     *
+     * @param request
+     */
+    protected CORSRequestType checkRequestType(final HttpServletRequest request) {
+        CORSRequestType requestType = CORSRequestType.INVALID_CORS;
+        if (request == null) {
+            throw new IllegalArgumentException(
+                    sm.getString("corsFilter.nullRequest"));
+        }
+        String originHeader = request.getHeader(REQUEST_HEADER_ORIGIN);
+        // Section 6.1.1 and Section 6.2.1
+        if (originHeader != null) {
+            if (originHeader.isEmpty()) {
+                requestType = CORSRequestType.INVALID_CORS;
+            } else if (!isValidOrigin(originHeader)) {
+                requestType = CORSRequestType.INVALID_CORS;
+            } else {
+                String method = request.getMethod();
+                if (method != null && HTTP_METHODS.contains(method)) {
+                    if ("OPTIONS".equals(method)) {
+                        String accessControlRequestMethodHeader =
+                                request.getHeader(
+                                        REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD);
+                        if (accessControlRequestMethodHeader != null &&
+                                !accessControlRequestMethodHeader.isEmpty()) {
+                            requestType = CORSRequestType.PRE_FLIGHT;
+                        } else if (accessControlRequestMethodHeader != null &&
+                                accessControlRequestMethodHeader.isEmpty()) {
+                            requestType = CORSRequestType.INVALID_CORS;
+                        } else {
+                            requestType = CORSRequestType.ACTUAL;
+                        }
+                    } else if ("GET".equals(method) || "HEAD".equals(method)) {
+                        requestType = CORSRequestType.SIMPLE;
+                    } else if ("POST".equals(method)) {
+                        String contentType = request.getContentType();
+                        if (contentType != null) {
+                            contentType = contentType.toLowerCase().trim();
+                            if (SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES
+                                    .contains(contentType)) {
+                                requestType = CORSRequestType.SIMPLE;
+                            } else {
+                                requestType = CORSRequestType.ACTUAL;
+                            }
+                        }
+                    } else if (COMPLEX_HTTP_METHODS.contains(method)) {
+                        requestType = CORSRequestType.ACTUAL;
+                    }
+                }
+            }
+        } else {
+            requestType = CORSRequestType.NOT_CORS;
+        }
+
+        return requestType;
+    }
+
+
+    /**
+     * Checks if the Origin is allowed to make a CORS request.
+     *
+     * @param origin
+     *            The Origin.
+     * @return <code>true</code> if origin is allowed; <code>false</code>
+     *         otherwise.
+     */
+    private boolean isOriginAllowed(final String origin) {
+        if (anyOriginAllowed) {
+            return true;
+        }
+
+        // If 'Origin' header is a case-sensitive match of any of allowed
+        // origins, then return true, else return false.
+        return allowedOrigins.contains(origin);
+    }
+
+
+    /**
+     * Parses each param-value and populates configuration variables. If a param
+     * is provided, it overrides the default.
+     *
+     * @param allowedOrigins
+     *            A {@link String} of comma separated origins.
+     * @param allowedHttpMethods
+     *            A {@link String} of comma separated HTTP methods.
+     * @param allowedHttpHeaders
+     *            A {@link String} of comma separated HTTP headers.
+     * @param exposedHeaders
+     *            A {@link String} of comma separated headers that needs to be
+     *            exposed.
+     * @param supportsCredentials
+     *            "true" if support credentials needs to be enabled.
+     * @param preflightMaxAge
+     *            The amount of seconds the user agent is allowed to cache the
+     *            result of the pre-flight request.
+     * @throws ServletException
+     */
+    private void parseAndStore(final String allowedOrigins,
+            final String allowedHttpMethods, final String allowedHttpHeaders,
+            final String exposedHeaders, final String supportsCredentials,
+            final String preflightMaxAge, final String decorateRequest)
+                    throws ServletException {
+        if (allowedOrigins != null) {
+            if (allowedOrigins.trim().equals("*")) {
+                this.anyOriginAllowed = true;
+            } else {
+                this.anyOriginAllowed = false;
+                Set<String> setAllowedOrigins =
+                        parseStringToSet(allowedOrigins);
+                this.allowedOrigins.clear();
+                this.allowedOrigins.addAll(setAllowedOrigins);
+            }
+        }
+
+        if (allowedHttpMethods != null) {
+            Set<String> setAllowedHttpMethods =
+                    parseStringToSet(allowedHttpMethods);
+            this.allowedHttpMethods.clear();
+            this.allowedHttpMethods.addAll(setAllowedHttpMethods);
+        }
+
+        if (allowedHttpHeaders != null) {
+            Set<String> setAllowedHttpHeaders =
+                    parseStringToSet(allowedHttpHeaders);
+            Set<String> lowerCaseHeaders = new HashSet<String>();
+            for (String header : setAllowedHttpHeaders) {
+                String lowerCase = header.toLowerCase();
+                lowerCaseHeaders.add(lowerCase);
+            }
+            this.allowedHttpHeaders.clear();
+            this.allowedHttpHeaders.addAll(lowerCaseHeaders);
+        }
+
+        if (exposedHeaders != null) {
+            Set<String> setExposedHeaders = parseStringToSet(exposedHeaders);
+            this.exposedHeaders.clear();
+            this.exposedHeaders.addAll(setExposedHeaders);
+        }
+
+        if (supportsCredentials != null) {
+            // For any value other then 'true' this will be false.
+            this.supportsCredentials = Boolean
+                    .parseBoolean(supportsCredentials);
+        }
+
+        if (preflightMaxAge != null) {
+            try {
+                if (!preflightMaxAge.isEmpty()) {
+                    this.preflightMaxAge = Long.parseLong(preflightMaxAge);
+                } else {
+                    this.preflightMaxAge = 0L;
+                }
+            } catch (NumberFormatException e) {
+                throw new ServletException(
+                        sm.getString("corsFilter.invalidPreflightMaxAge"), e);
+            }
+        }
+
+        if (decorateRequest != null) {
+            // For any value other then 'true' this will be false.
+            this.decorateRequest = Boolean.parseBoolean(decorateRequest);
+        }
+    }
+
+    /**
+     * Takes a comma separated list and returns a Set<String>.
+     *
+     * @param data
+     *            A comma separated list of strings.
+     * @return Set<String>
+     */
+    private Set<String> parseStringToSet(final String data) {
+        String[] splits;
+
+        if (data != null && data.length() > 0) {
+            splits = data.split(",");
+        } else {
+            splits = new String[] {};
+        }
+
+        Set<String> set = new HashSet<String>();
+        if (splits.length > 0) {
+            for (String split : splits) {
+                set.add(split.trim());
+            }
+        }
+
+        return set;
+    }
+
+
+    /**
+     * Checks if a given origin is valid or not. Criteria:
+     * <ul>
+     * <li>If an encoded character is present in origin, it's not valid.</li>
+     * <li>Origin should be a valid {@link URI}</li>
+     * </ul>
+     *
+     * @param origin
+     * @see <a href="http://tools.ietf.org/html/rfc952">RFC952</a>
+     */
+    protected static boolean isValidOrigin(String origin) {
+        // Checks for encoded characters. Helps prevent CRLF injection.
+        if (origin.contains("%")) {
+            return false;
+        }
+
+        URI originURI;
+
+        try {
+            originURI = new URI(origin);
+        } catch (URISyntaxException e) {
+            return false;
+        }
+        // If scheme for URI is null, return false. Return true otherwise.
+        return originURI.getScheme() != null;
+
+    }
+
+
+    /**
+     * Determines if any origin is allowed to make CORS request.
+     *
+     * @return <code>true</code> if it's enabled; false otherwise.
+     */
+    public boolean isAnyOriginAllowed() {
+        return anyOriginAllowed;
+    }
+
+
+    /**
+     * Returns a {@link Set} of headers that should be exposed by browser.
+     */
+    public Collection<String> getExposedHeaders() {
+        return exposedHeaders;
+    }
+
+
+    /**
+     * Determines is supports credentials is enabled.
+     */
+    public boolean isSupportsCredentials() {
+        return supportsCredentials;
+    }
+
+
+    /**
+     * Returns the preflight response cache time in seconds.
+     *
+     * @return Time to cache in seconds.
+     */
+    public long getPreflightMaxAge() {
+        return preflightMaxAge;
+    }
+
+
+    /**
+     * Returns the {@link Set} of allowed origins that are allowed to make
+     * requests.
+     *
+     * @return {@link Set}
+     */
+    public Collection<String> getAllowedOrigins() {
+        return allowedOrigins;
+    }
+
+
+    /**
+     * Returns a {@link Set} of HTTP methods that are allowed to make requests.
+     *
+     * @return {@link Set}
+     */
+    public Collection<String> getAllowedHttpMethods() {
+        return allowedHttpMethods;
+    }
+
+
+    /**
+     * Returns a {@link Set} of headers support by resource.
+     *
+     * @return {@link Set}
+     */
+    public Collection<String> getAllowedHttpHeaders() {
+        return allowedHttpHeaders;
+    }
+
+
+    // -------------------------------------------------- CORS Response Headers
+    /**
+     * The Access-Control-Allow-Origin header indicates whether a resource can
+     * be shared based by returning the value of the Origin request header in
+     * the response.
+     */
+    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN =
+            "Access-Control-Allow-Origin";
+
+    /**
+     * The Access-Control-Allow-Credentials header indicates whether the
+     * response to request can be exposed when the omit credentials flag is
+     * unset. When part of the response to a preflight request it indicates that
+     * the actual request can include user credentials.
+     */
+    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS =
+            "Access-Control-Allow-Credentials";
+
+    /**
+     * The Access-Control-Expose-Headers header indicates which headers are safe
+     * to expose to the API of a CORS API specification
+     */
+    public static final String RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS =
+            "Access-Control-Expose-Headers";
+
+    /**
+     * The Access-Control-Max-Age header indicates how long the results of a
+     * preflight request can be cached in a preflight result cache.
+     */
+    public static final String RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE =
+            "Access-Control-Max-Age";
+
+    /**
+     * The Access-Control-Allow-Methods header indicates, as part of the
+     * response to a preflight request, which methods can be used during the
+     * actual request.
+     */
+    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_METHODS =
+            "Access-Control-Allow-Methods";
+
+    /**
+     * The Access-Control-Allow-Headers header indicates, as part of the
+     * response to a preflight request, which header field names can be used
+     * during the actual request.
+     */
+    public static final String RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_HEADERS =
+            "Access-Control-Allow-Headers";
+
+    // -------------------------------------------------- CORS Request Headers
+    /**
+     * The Origin header indicates where the cross-origin request or preflight
+     * request originates from.
+     */
+    public static final String REQUEST_HEADER_ORIGIN = "Origin";
+
+    /**
+     * The Access-Control-Request-Method header indicates which method will be
+     * used in the actual request as part of the preflight request.
+     */
+    public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD =
+            "Access-Control-Request-Method";
+
+    /**
+     * The Access-Control-Request-Headers header indicates which headers will be
+     * used in the actual request as part of the preflight request.
+     */
+    public static final String REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS =
+            "Access-Control-Request-Headers";
+
+    // ----------------------------------------------------- Request attributes
+    /**
+     * The prefix to a CORS request attribute.
+     */
+    public static final String HTTP_REQUEST_ATTRIBUTE_PREFIX = "cors.";
+
+    /**
+     * Attribute that contains the origin of the request.
+     */
+    public static final String HTTP_REQUEST_ATTRIBUTE_ORIGIN =
+            HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.origin";
+
+    /**
+     * Boolean value, suggesting if the request is a CORS request or not.
+     */
+    public static final String HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST =
+            HTTP_REQUEST_ATTRIBUTE_PREFIX + "isCorsRequest";
+
+    /**
+     * Type of CORS request, of type {@link CORSRequestType}.
+     */
+    public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE =
+            HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.type";
+
+    /**
+     * Request headers sent as 'Access-Control-Request-Headers' header, for
+     * pre-flight request.
+     */
+    public static final String HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS =
+            HTTP_REQUEST_ATTRIBUTE_PREFIX + "request.headers";
+
+    // -------------------------------------------------------------- Constants
+    /**
+     * Enumerates varies types of CORS requests. Also, provides utility methods
+     * to determine the request type.
+     */
+    protected static enum CORSRequestType {
+        /**
+         * A simple HTTP request, i.e. it shouldn't be pre-flighted.
+         */
+        SIMPLE,
+        /**
+         * A HTTP request that needs to be pre-flighted.
+         */
+        ACTUAL,
+        /**
+         * A pre-flight CORS request, to get meta information, before a
+         * non-simple HTTP request is sent.
+         */
+        PRE_FLIGHT,
+        /**
+         * Not a CORS request, but a normal request.
+         */
+        NOT_CORS,
+        /**
+         * An invalid CORS request, i.e. it qualifies to be a CORS request, but
+         * fails to be a valid one.
+         */
+        INVALID_CORS
+    }
+
+    /**
+     * {@link Collection} of HTTP methods. Case sensitive.
+     *
+     * @see  <a href="http://tools.ietf.org/html/rfc2616#section-5.1.1"
+     *       >http://tools.ietf.org/html/rfc2616#section-5.1.1</a>
+     *
+     */
+    public static final Collection<String> HTTP_METHODS =
+            new HashSet<String>(Arrays.asList("OPTIONS", "GET", "HEAD", "POST",
+                    "PUT", "DELETE", "TRACE", "CONNECT"));
+    /**
+     * {@link Collection} of non-simple HTTP methods. Case sensitive.
+     */
+    public static final Collection<String> COMPLEX_HTTP_METHODS =
+            new HashSet<String>(Arrays.asList("PUT", "DELETE", "TRACE",
+                    "CONNECT"));
+    /**
+     * {@link Collection} of Simple HTTP methods. Case sensitive.
+     *
+     * @see  <a href="http://www.w3.org/TR/cors/#terminology"
+     *       >http://www.w3.org/TR/cors/#terminology</a>
+     */
+    public static final Collection<String> SIMPLE_HTTP_METHODS =
+            new HashSet<String>(Arrays.asList("GET", "POST", "HEAD"));
+
+    /**
+     * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
+     *
+     * @see  <a href="http://www.w3.org/TR/cors/#terminology"
+     *       >http://www.w3.org/TR/cors/#terminology</a>
+     */
+    public static final Collection<String> SIMPLE_HTTP_REQUEST_HEADERS =
+            new HashSet<String>(Arrays.asList("Accept", "Accept-Language",
+                    "Content-Language"));
+
+    /**
+     * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
+     *
+     * @see  <a href="http://www.w3.org/TR/cors/#terminology"
+     *       >http://www.w3.org/TR/cors/#terminology</a>
+     */
+    public static final Collection<String> SIMPLE_HTTP_RESPONSE_HEADERS =
+            new HashSet<String>(Arrays.asList("Cache-Control",
+                    "Content-Language", "Content-Type", "Expires",
+                    "Last-Modified", "Pragma"));
+
+    /**
+     * {@link Collection} of Simple HTTP request headers. Case in-sensitive.
+     *
+     * @see  <a href="http://www.w3.org/TR/cors/#terminology"
+     *       >http://www.w3.org/TR/cors/#terminology</a>
+     */
+    public static final Collection<String> SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES =
+            new HashSet<String>(Arrays.asList(
+                    "application/x-www-form-urlencoded",
+                    "multipart/form-data", "text/plain"));
+
+    // ------------------------------------------------ Configuration Defaults
+    /**
+     * By default, all origins are allowed to make requests.
+     */
+    public static final String DEFAULT_ALLOWED_ORIGINS = "*";
+
+    /**
+     * By default, following methods are supported: GET, POST, HEAD and OPTIONS.
+     */
+    public static final String DEFAULT_ALLOWED_HTTP_METHODS =
+            "GET,POST,HEAD,OPTIONS";
+
+    /**
+     * By default, time duration to cache pre-flight response is 30 mins.
+     */
+    public static final String DEFAULT_PREFLIGHT_MAXAGE = "1800";
+
+    /**
+     * By default, support credentials is turned on.
+     */
+    public static final String DEFAULT_SUPPORTS_CREDENTIALS = "true";
+
+    /**
+     * By default, following headers are supported:
+     * Origin,Accept,X-Requested-With, Content-Type,
+     * Access-Control-Request-Method, and Access-Control-Request-Headers.
+     */
+    public static final String DEFAULT_ALLOWED_HTTP_HEADERS =
+            "Origin,Accept,X-Requested-With,Content-Type," +
+            "Access-Control-Request-Method,Access-Control-Request-Headers";
+
+    /**
+     * By default, none of the headers are exposed in response.
+     */
+    public static final String DEFAULT_EXPOSED_HEADERS = "";
+
+    /**
+     * By default, request is decorated with CORS attributes.
+     */
+    public static final String DEFAULT_DECORATE_REQUEST = "true";
+
+    // ----------------------------------------Filter Config Init param-name(s)
+    /**
+     * Key to retrieve allowed origins from {@link FilterConfig}.
+     */
+    public static final String PARAM_CORS_ALLOWED_ORIGINS =
+            "cors.allowed.origins";
+
+    /**
+     * Key to retrieve support credentials from {@link FilterConfig}.
+     */
+    public static final String PARAM_CORS_SUPPORT_CREDENTIALS =
+            "cors.support.credentials";
+
+    /**
+     * Key to retrieve exposed headers from {@link FilterConfig}.
+     */
+    public static final String PARAM_CORS_EXPOSED_HEADERS =
+            "cors.exposed.headers";
+
+    /**
+     * Key to retrieve allowed headers from {@link FilterConfig}.
+     */
+    public static final String PARAM_CORS_ALLOWED_HEADERS =
+            "cors.allowed.headers";
+
+    /**
+     * Key to retrieve allowed methods from {@link FilterConfig}.
+     */
+    public static final String PARAM_CORS_ALLOWED_METHODS =
+            "cors.allowed.methods";
+
+    /**
+     * Key to retrieve preflight max age from {@link FilterConfig}.
+     */
+    public static final String PARAM_CORS_PREFLIGHT_MAXAGE =
+            "cors.preflight.maxage";
+
+    /**
+     * Key to determine if request should be decorated.
+     */
+    public static final String PARAM_CORS_REQUEST_DECORATE =
+            "cors.request.decorate";
+}