Introduce new CORS interceptor and switch examples to use it

This commit is contained in:
James Agnew 2016-11-21 18:30:51 +01:00
parent 2d52affd8d
commit 888f42a032
28 changed files with 392 additions and 1427 deletions

View File

@ -73,6 +73,10 @@
<version>3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
<reporting>

View File

@ -1,17 +1,16 @@
package example;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhir.validation.ResultSeverityEnum;
@SuppressWarnings("serial")
@ -119,4 +118,38 @@ public class ServletExamples {
}
// END SNIPPET: responseHighlighterInterceptor
// START SNIPPET: corsInterceptor
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithCors extends RestfulServer {
@Override
protected void initialize() throws ServletException {
// ... define your resource providers here ...
// Define your CORS configuration. This is an example
// showing a typical setup. You should customize this
// to your specific needs
CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
// Create the interceptor and register it
CorsInterceptor interceptor = new CorsInterceptor(config);
registerInterceptor(interceptor);
}
}
// END SNIPPET: corsInterceptor
}

View File

@ -43,15 +43,6 @@
<optional>true</optional>
</dependency>
<!-- Only required for CORS support -->
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<optional>true</optional>
<!-- <exclusions> <exclusion> <artifactId>servlet-api</artifactId> <groupId>javax.servlet</groupId>
</exclusion> </exclusions> -->
</dependency>
<!-- Only required for Schematron Validator Support -->
<dependency>
<groupId>com.phloc</groupId>
@ -123,6 +114,17 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- Server -->
<dependency>

View File

@ -0,0 +1,71 @@
package ca.uhn.fhir.rest.server.interceptor;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.Validate;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsProcessor;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.DefaultCorsProcessor;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class CorsInterceptor extends InterceptorAdapter {
private CorsProcessor myCorsProcessor;
private CorsConfiguration myConfig;
/**
* Constructor
*/
public CorsInterceptor() {
this(new CorsConfiguration());
}
/**
* Constructor
*
* @param theConfiguration
* The CORS configuration
*/
public CorsInterceptor(CorsConfiguration theConfiguration) {
Validate.notNull(theConfiguration, "theConfiguration must not be null");
myCorsProcessor = new DefaultCorsProcessor();
setConfig(theConfiguration);
}
/**
* Sets the CORS configuration
*/
public void setConfig(CorsConfiguration theConfiguration) {
myConfig = theConfiguration;
}
/**
* Gets the CORS configuration
*/
public CorsConfiguration getConfig() {
return myConfig;
}
@Override
public boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse) {
if (CorsUtils.isCorsRequest(theRequest)) {
boolean isValid;
try {
isValid = myCorsProcessor.processRequest(myConfig, theRequest, theResponse);
} catch (IOException e) {
throw new InternalErrorException(e);
}
if (!isValid || CorsUtils.isPreFlightRequest(theRequest)) {
return false;
}
}
return super.incomingRequestPreProcessed(theRequest, theResponse);
}
}

View File

@ -171,10 +171,6 @@
<artifactId>thymeleaf-spring4</artifactId>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
</dependency>
<dependency>
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>

View File

@ -147,11 +147,6 @@
<groupId>com.phloc</groupId>
<artifactId>phloc-schematron</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.jpa.demo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@ -9,6 +10,7 @@ import javax.servlet.ServletException;
import org.hl7.fhir.dstu3.model.Meta;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@ -30,6 +32,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class JpaServerDemo extends RestfulServer {
@ -47,7 +50,7 @@ public class JpaServerDemo extends RestfulServer {
// Get the spring context from the web container (it's declared in web.xml)
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
/*
* The hapi-fhir-server-resourceproviders-dev.xml file is a spring configuration
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and
@ -139,6 +142,22 @@ public class JpaServerDemo extends RestfulServer {
*/
setPagingProvider(new FifoMemoryPagingProvider(10));
// Register a CORS filter
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
registerInterceptor(corsInterceptor);
/*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
*/

View File

@ -61,52 +61,4 @@
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
<filter>
<filter-name>CORS Filter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
<param-name>cors.allowed.headers</param-name>
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
<param-name>cors.exposed.headers</param-name>
<param-value>Location,Content-Location</param-value>
</init-param>
<init-param>
<description>A flag that suggests if CORS is supported with cookies</description>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>A flag to control logging</description>
<param-name>cors.logging.enabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
<param-name>cors.preflight.maxage</param-name>
<param-value>300</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -130,12 +130,14 @@
<artifactId>commons-dbcp2</artifactId>
<scope>test</scope>
</dependency>
<!--
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<scope>test</scope>
</dependency>
-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>

View File

@ -1,8 +1,12 @@
package ca.uhn.fhir.jpa.config;
import javax.el.ExpressionFactory;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DispatcherServletConfig {
//nothing
ExpressionFactory myFacg;
}

View File

@ -3,18 +3,14 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import org.apache.catalina.filters.CorsFilter;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
@ -28,6 +24,7 @@ import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.servlet.DispatcherServlet;
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config;
@ -40,6 +37,7 @@ import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.util.TestUtil;
public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
@ -116,16 +114,22 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
subsServletHolder.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, WebsocketDstu3Config.class.getName());
proxyHandler.addServlet(subsServletHolder, "/*");
FilterHolder corsFilterHolder = new FilterHolder();
corsFilterHolder.setHeldClass(CorsFilter.class);
corsFilterHolder.setInitParameter("cors.allowed.origins", "*");
corsFilterHolder.setInitParameter("cors.allowed.methods", "GET,POST,PUT,DELETE,OPTIONS");
corsFilterHolder.setInitParameter("cors.allowed.headers", "X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers");
corsFilterHolder.setInitParameter("cors.exposed.headers", "Location,Content-Location");
corsFilterHolder.setInitParameter("cors.support.credentials", "true");
corsFilterHolder.setInitParameter("cors.logging.enabled", "true");
proxyHandler.addFilter(corsFilterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
// Register a CORS filter
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
ourRestServer.registerInterceptor(corsInterceptor);
server.setHandler(proxyHandler);
server.start();

View File

@ -151,17 +151,6 @@
<artifactId>phloc-schematron</artifactId>
</dependency>
<!--
Used for CORS support
If you are deploying your project to Apache Tomcat, you can
comment this dependency out since the tomcat CORS filter will
already be available on the classpath
-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
</dependency>
</dependencies>
<build>

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.demo;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@ -8,6 +9,7 @@ import javax.servlet.ServletException;
import org.hl7.fhir.dstu3.model.Meta;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@ -29,6 +31,7 @@ import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class JpaServerDemo extends RestfulServer {
@ -140,6 +143,23 @@ public class JpaServerDemo extends RestfulServer {
*/
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
* Enable CORS
*/
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
registerInterceptor(corsInterceptor);
/*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
*/

View File

@ -159,17 +159,6 @@
<artifactId>commons-dbcp2</artifactId>
</dependency>
<!--
Only required for CORS support - Change the scope from
"provided" to "compile" if you are deploying to something
other than Tomcat
-->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

View File

@ -1,6 +1,7 @@
package ca.uhn.fhirtest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@ -12,6 +13,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.WebsocketDstu2Config;
@ -27,19 +29,15 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.TestDstu3Config;
import ca.uhn.fhirtest.config.TdlDstu2Config;
import ca.uhn.fhirtest.config.TdlDstu3Config;
import ca.uhn.fhirtest.config.TestDstu2Config;
import ca.uhn.fhirtest.config.TestDstu3Config;
public class TestRestfulServer extends RestfulServer {
@ -172,6 +170,23 @@ public class TestRestfulServer extends RestfulServer {
setPlainProviders(plainProviders);
/*
* Enable CORS
*/
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
registerInterceptor(corsInterceptor);
/*
* We want to format the response using nice HTML if it's a browser, since this
* makes things a little easier for testers.

View File

@ -76,6 +76,7 @@
<load-on-startup>1</load-on-startup>
</servlet>
<!--
<servlet>
<servlet-name>fhirServletTdl2</servlet-name>
<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
@ -89,7 +90,6 @@
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>fhirServletTdl3</servlet-name>
<servlet-class>ca.uhn.fhirtest.TestRestfulServer</servlet-class>
@ -103,6 +103,7 @@
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
-->
<servlet-mapping>
<servlet-name>fhirServletDstu1</servlet-name>
@ -132,11 +133,11 @@
<url-pattern>/baseStu3/*</url-pattern>
</servlet-mapping>
<!--
<servlet-mapping>
<servlet-name>fhirServletTdl2</servlet-name>
<url-pattern>/testDataLibraryDstu2/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>fhirServletTdl3</servlet-name>
<url-pattern>/testDataLibraryDstu3/*</url-pattern>
@ -145,6 +146,7 @@
<servlet-name>fhirServletTdl3</servlet-name>
<url-pattern>/testDataLibraryStu3/*</url-pattern>
</servlet-mapping>
-->
<servlet-mapping>
<servlet-name>fhirServletDstu2</servlet-name>
@ -156,52 +158,4 @@
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
<filter>
<filter-name>CORS Filter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
<param-name>cors.allowed.methods</param-name>
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
</init-param>
<init-param>
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
<param-name>cors.allowed.headers</param-name>
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
</init-param>
<init-param>
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
<param-name>cors.exposed.headers</param-name>
<param-value>Location,Content-Location</param-value>
</init-param>
<init-param>
<description>A flag that suggests if CORS is supported with cookies</description>
<param-name>cors.support.credentials</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>A flag to control logging</description>
<param-name>cors.logging.enabled</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
<param-name>cors.preflight.maxage</param-name>
<param-value>300</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>2.1-SNAPSHOT</version>
<version>2.2-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
@ -19,18 +19,15 @@
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<version>2.1-SNAPSHOT</version>
</dependency>
<!-- conformance profile -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu2</artifactId>
<version>2.1-SNAPSHOT</version>
<version>2.2-SNAPSHOT</version>
</dependency>
<!-- Unit test dependencies -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-structures-dstu3</artifactId>
<version>2.1-SNAPSHOT</version>
<version>2.2-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -73,11 +73,6 @@
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
@ -94,13 +89,13 @@
<scope>test</scope>
</dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<!-- This is used for cors -->
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
<!-- UNIT TEST DEPENDENCIES -->
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>

View File

@ -1,15 +1,15 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import org.apache.catalina.filters.CorsFilter;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
@ -29,11 +29,14 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.server.RestfulServerSelfReferenceTest.DummyPatientResourceProvider;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
@ -55,11 +58,26 @@ public class CorsTest {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals("GET", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("GET,POST,PUT,DELETE,OPTIONS", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("null", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue());
}
}
@Test
public void testRequestWithInvalidOrigin() throws ClientProtocolException, IOException {
{
HttpOptions httpOpt = new HttpOptions(ourBaseUri + "/Organization/b27ed191-f62d-4128-d99d-40b5e84f2bf2");
httpOpt.addHeader("Access-Control-Request-Method", "GET");
httpOpt.addHeader("Origin", "http://yahoo.com");
httpOpt.addHeader("Access-Control-Request-Headers", "accept, x-fhir-starter, content-type");
HttpResponse status = ourClient.execute(httpOpt);
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(403, status.getStatusLine().getStatusCode());
}
}
@Test
public void testContextWithSpace() throws Exception {
{
@ -71,7 +89,7 @@ public class CorsTest {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals("POST", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("GET,POST,PUT,DELETE,OPTIONS", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("http://www.fhir-starter.com", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue());
}
{
@ -113,6 +131,13 @@ public class CorsTest {
ourClient.close();
}
@Test
public void testCorsConfigMethods() {
CorsInterceptor corsInterceptor = new CorsInterceptor();
assertNotNull(corsInterceptor.getConfig());
corsInterceptor.setConfig(new CorsConfiguration());
}
@BeforeClass
public static void beforeClass() throws Exception {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
@ -129,18 +154,26 @@ public class CorsTest {
// ServletHandler proxyHandler = new ServletHandler();
ServletHolder servletHolder = new ServletHolder(restServer);
FilterHolder fh = new FilterHolder();
fh.setHeldClass(CorsFilter.class);
fh.setInitParameter("cors.logging.enabled", "true");
fh.setInitParameter("cors.allowed.origins", "*");
fh.setInitParameter("cors.allowed.headers", "x-fhir-starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers");
fh.setInitParameter("cors.exposed.headers", "Location,Content-Location");
fh.setInitParameter("cors.allowed.methods", "GET,POST,PUT,DELETE,OPTIONS");
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor interceptor = new CorsInterceptor(config);
config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("http://www.fhir-starter.com");
config.addAllowedOrigin("null");
config.addAllowedOrigin("file://");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
restServer.registerInterceptor(interceptor);
ServletContextHandler ch = new ServletContextHandler();
ch.setContextPath("/rootctx/rcp2");
ch.addServlet(servletHolder, "/fhirctx/fcp2/*");
ch.addFilter(fh, "/*", EnumSet.of(DispatcherType.INCLUDE, DispatcherType.REQUEST));
ContextHandlerCollection contexts = new ContextHandlerCollection();
ourServer.setHandler(contexts);

View File

@ -81,11 +81,6 @@
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
@ -142,6 +137,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -16,11 +16,7 @@ import static org.mockito.Mockito.when;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
@ -33,7 +29,6 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.ebaysf.web.cors.CORSFilter;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
@ -42,6 +37,7 @@ import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.web.cors.CorsConfiguration;
import com.phloc.commons.collections.iterate.ArrayEnumeration;
@ -62,11 +58,7 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.server.BundleInclusionRule;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.PortUtil;
@ -708,14 +700,30 @@ public class ResponseHighlightingInterceptorTest {
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
/*
* Enable CORS
*/
CorsConfiguration config = new CorsConfiguration();
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
config.addAllowedHeader("Origin");
config.addAllowedHeader("Accept");
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedHeader("Access-Control-Request-Method");
config.addAllowedHeader("Access-Control-Request-Headers");
config.addAllowedOrigin("*");
config.addExposedHeader("Location");
config.addExposedHeader("Content-Location");
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
ourServlet.registerInterceptor(corsInterceptor);
ourServlet.registerInterceptor(new ResponseHighlighterInterceptor());
ourServlet.setResourceProviders(patientProvider, new DummyBinaryResourceProvider());
ourServlet.setBundleInclusionRule(BundleInclusionRule.BASED_ON_RESOURCE_PRESENCE);
ServletHolder servletHolder = new ServletHolder(ourServlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
proxyHandler.addFilterWithMapping(CORSFilter.class, "/*", 1);
ourServer.setHandler(proxyHandler);
ourServer.start();

View File

@ -95,11 +95,6 @@
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
@ -153,6 +148,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<reporting>

View File

@ -94,11 +94,6 @@
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
@ -155,6 +150,11 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<reporting>

14
pom.xml
View File

@ -309,8 +309,6 @@
<phloc_commons_version>4.4.5</phloc_commons_version>
<spring_version>4.3.1.RELEASE</spring_version>
<thymeleaf-version>3.0.1.RELEASE</thymeleaf-version>
<tomcat_version>8.0.39</tomcat_version>
<ebay_cors_filter_version>1.0.1</ebay_cors_filter_version>
<xmlunit_version>1.6</xmlunit_version>
<!-- We are aiming to still work on a very old version of SLF4j even though we depend on the newest, just to be nice to users of the API. This version is tested in the hapi-fhir-cobertura. -->
@ -526,12 +524,6 @@
<artifactId>wagon-scm</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<!-- Use property for version because we should refer to this from docs -->
<version>${tomcat_version}</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
@ -547,11 +539,6 @@
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
<dependency>
<groupId>org.ebaysf.web</groupId>
<artifactId>cors-filter</artifactId>
<version>${ebay_cors_filter_version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-http</artifactId>
@ -1592,6 +1579,7 @@
<module>hapi-fhir-base-test-mindeps-server</module>
<module>hapi-tinder-plugin</module>
<module>hapi-tinder-test</module>
<module>hapi-fhir-narrativegenerator</module>
<module>hapi-fhir-structures-dstu</module>
<module>hapi-fhir-validation-resources-dstu2</module>
<module>hapi-fhir-structures-dstu2</module>

View File

@ -30,11 +30,13 @@
to GitHub user @vijayt27 for reporting!
</action>
<action type="add">
All server examples as well as the CLI have been
switched from using eBay's CORS filter to using
the Apache Tomcat CORS filter instead. The former
has become unmaintained and has unfixed bugs so
it is no longer recommended for use.
As the eBay CORS interceptor has gone dormant, we have introduced a new
HAPI server interceptor which can be used to implement CORS support
instead of using the previously recommended Servlet Filter. All server
examples as well as the CLI have been switched to use this new interceptor.
See the
<![CDATA[<a href="./doc_cors.html">CORS Documentation</a>]]>
for more information.
</action>
<action type="fix" issue="480">
Make the parser configurable so that when

View File

@ -10,13 +10,7 @@
<section name="CORS">
<p>
If you are intending to support JavaScript clients in your server application,
you will need to enable Cross Origin Resource Sharing (CORS). There are
a number of ways of supporting this, but the easiest is to use a servlet filter.
</p>
<p>
<p class="doc_info_bubble">
Note that in previous revisions of this document we recommended using the
<a href="https://github.com/ebay/cors-filter">eBay CORS Filter</a>, but
as of 2016 the eBay filter is no longer being maintained and contains known bugs.
@ -24,35 +18,80 @@
</p>
<p>
The following examples show how to use the Apache Tomcat CorsFilter to enable
CORS support. The instructions below should work even on platforms other than
Tomcat (in other words, you can deploy the Tomcat CorsFilter to Jetty or JBoss if you like)
but if you run into conflicts it may be worth investigating if there is a dedicated
CORS filter for the platform you are using.
If you are intending to support JavaScript clients in your server application,
you will generally need to enable Cross Origin Resource Sharing (CORS). There are
a number of ways of supporting this, so two are shown here:
</p>
<ul>
<li>An approach using a HAPI FHIR Server Interceptor (Requires SpringFramework)</li>
<li>An approach using a servlet Filter (Container Specific)</li>
</ul>
<subsection name="Add the Dependency">
<subsection name="HAPI FHIR Server Interceptor">
<p>
If you are deploying to a platform other than Tomcat, add the
following dependency to your Maven POM. If you are deploying
to Tomcat, the required classes are present on the classpath
so youdo not need to do this step.
The HAPI FHIR server framework includes an interceptor that can be
used to provide CORS functionality on your server. This mechanism is
nice because it relies purely on Java configuration (no messing around with
web.xml files). HAPI's interceptor is a thin wrapper around Spring Framework's
CorsProcessor class, so it requires Spring to be present on your classpath.
</p>
<p>
Add the following dependency to your POM:
Spring is generally unlikely to conflict with other libraries so it is usually
safe to add it to your classpath, but it is a fairly large library so if size is
a concern you might opt to use a filter instead.
</p>
<p>
The following steps outline how to enable HAPI's CorsInterceptor:
</p>
<p>
Add the following dependency to your POM. Note the exclusion of
commons-logging, as we are using SLF4j without commons-logging in
most of our examples. If your application uses commons-logging you don't need
to exclude that dependency.
</p>
<source><![CDATA[<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>${tomcat_version}</version>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring_version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>]]></source>
<p>
In your server's initialization method, create and register
a CorsInterceptor:
</p>
<macro name="snippet">
<param name="id" value="corsInterceptor" />
<param name="file" value="examples/src/main/java/example/ServletExamples.java" />
</macro>
</subsection>
<subsection name="Add the filter to your web.xml">
<subsection name="Add the Dependency">
<p>
The following examples show how to use the Apache Tomcat CorsFilter to enable
CORS support. The filter being used
(<code>org.apache.catalina.filters.CorsFilter</code>) is bundled with Apache
Tomcat so if you are deploying to that server you can use the filter.
</p>
<p>
Other containers have similar filters you can use, so consult the documentation
for the given container you are using for more information. (If you have
an example for how to configure a different CORS filter, please send it
our way! Examples are always useful!)
</p>
<p>
In your web.xml file (within the WEB-INF directory in your WAR file),
the following filter definition adds the CORS filter, including support

View File

@ -290,6 +290,17 @@
</subsection>
<subsection name="CORS (Cross-Origin Resource Sharing)">
<p>
HAPI FHIR includes an interceptor which can be used to
implement CORS support on your server. See HAPI's
<a href="./doc_cors.html">CORS Documentation</a> for information
on how to use this interceptor.
</p>
</subsection>
<subsection name="Rejecting Unsupported HTTP Verbs">
<p>