Improve testing on CorsInterceptor

This commit is contained in:
jamesagnew 2016-11-24 19:25:37 -05:00
parent ca602bdc1e
commit 69871bb8c2
2 changed files with 190 additions and 62 deletions

View File

@ -19,14 +19,50 @@ public class CorsInterceptor extends InterceptorAdapter {
private CorsConfiguration myConfig; private CorsConfiguration myConfig;
/** /**
* Constructor * Constructor which creates an interceptor with default CORS configuration for use in
* a FHIR server. This includes:
* <ul>
* <li>Allowed Origin: *</li>
* <li>Allowed Header: Origin</li>
* <li>Allowed Header: Accept</li>
* <li>Allowed Header: X-Requested-With</li>
* <li>Allowed Header: Content-Type</li>
* <li>Allowed Header: Access-Control-Request-Method</li>
* <li>Allowed Header: Access-Control-Request-Headers</li>
* <li>Exposed Header: Location</li>
* <li>Exposed Header: Content-Location</li>
* </ul>
* Note that this configuration is useful for quickly getting CORS working, but
* in a real production system you probably want to consider whether it is
* appropriate for your situation. In particular, using "Allowed Origin: *"
* isn't always the right thing to do.
*/ */
public CorsInterceptor() { public CorsInterceptor() {
this(new CorsConfiguration()); this(createDefaultCorsConfig());
}
private static CorsConfiguration createDefaultCorsConfig() {
CorsConfiguration retVal = new CorsConfiguration();
// *********************************************************
// Update constructor documentation if you change these:
// *********************************************************
retVal.addAllowedHeader("Origin");
retVal.addAllowedHeader("Accept");
retVal.addAllowedHeader("X-Requested-With");
retVal.addAllowedHeader("Content-Type");
retVal.addAllowedHeader("Access-Control-Request-Method");
retVal.addAllowedHeader("Access-Control-Request-Headers");
retVal.addAllowedOrigin("*");
retVal.addExposedHeader("Location");
retVal.addExposedHeader("Content-Location");
return retVal;
} }
/** /**
* Constructor * Constructor which accepts the given configuration
* *
* @param theConfiguration * @param theConfiguration
* The CORS configuration * The CORS configuration

View File

@ -1,15 +1,17 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server.interceptor;
import static org.junit.Assert.*; import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
@ -23,60 +25,41 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.HumanName;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Identifier;
import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.server.RestfulServerSelfReferenceTest.DummyPatientResourceProvider; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
public class CorsTest { public class CorsInterceptorDstu3Test {
private static CloseableHttpClient ourClient;
private static Server ourServer;
private static String ourBaseUri; private static String ourBaseUri;
private static FhirContext ourCtx = FhirContext.forDstu1(); private static CloseableHttpClient ourClient;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CorsTest.class); private static FhirContext ourCtx = FhirContext.forDstu3();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CorsInterceptorDstu3Test.class);
@Test private static Server ourServer;
public void testRequestWithNullOrigin() throws ClientProtocolException, IOException {
{
HttpOptions httpOpt = new HttpOptions(ourBaseUri + "/Organization/b27ed191-f62d-4128-d99d-40b5e84f2bf2");
httpOpt.addHeader("Access-Control-Request-Method", "GET");
httpOpt.addHeader("Origin", "null");
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("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 @Test
public void testContextWithSpace() throws Exception { public void testContextWithSpace() throws Exception {
@ -107,9 +90,9 @@ public class CorsTest {
ourLog.info("Response was:\n{}", responseContent); ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode()); assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent); Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, responseContent);
assertEquals(1, bundle.getEntries().size()); assertEquals(1, bundle.getEntry().size());
} }
{ {
HttpPost httpOpt = new HttpPost(ourBaseUri + "/Patient"); HttpPost httpOpt = new HttpPost(ourBaseUri + "/Patient");
@ -126,11 +109,6 @@ public class CorsTest {
} }
} }
public static void afterClass() throws Exception {
ourServer.stop();
ourClient.close();
}
@Test @Test
public void testCorsConfigMethods() { public void testCorsConfigMethods() {
CorsInterceptor corsInterceptor = new CorsInterceptor(); CorsInterceptor corsInterceptor = new CorsInterceptor();
@ -138,6 +116,54 @@ public class CorsTest {
corsInterceptor.setConfig(new CorsConfiguration()); corsInterceptor.setConfig(new CorsConfiguration());
} }
@Test
public void testDefaultConfig() {
CorsInterceptor def = new CorsInterceptor();
assertThat(def.getConfig().getAllowedOrigins(), contains("*"));
}
@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 testRequestWithNullOrigin() throws ClientProtocolException, IOException {
{
HttpOptions httpOpt = new HttpOptions(ourBaseUri + "/Organization/b27ed191-f62d-4128-d99d-40b5e84f2bf2");
httpOpt.addHeader("Access-Control-Request-Method", "GET");
httpOpt.addHeader("Origin", "null");
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("GET,POST,PUT,DELETE,OPTIONS", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_METHODS).getValue());
assertEquals("null", status.getFirstHeader(Constants.HEADER_CORS_ALLOW_ORIGIN).getValue());
}
}
public static void afterClass() throws Exception {
ourServer.stop();
ourClient.close();
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
@BeforeClass @BeforeClass
public static void beforeClass() throws Exception { public static void beforeClass() throws Exception {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
@ -151,7 +177,6 @@ public class CorsTest {
RestfulServer restServer = new RestfulServer(ourCtx); RestfulServer restServer = new RestfulServer(ourCtx);
restServer.setResourceProviders(new DummyPatientResourceProvider()); restServer.setResourceProviders(new DummyPatientResourceProvider());
// ServletHandler proxyHandler = new ServletHandler();
ServletHolder servletHolder = new ServletHolder(restServer); ServletHolder servletHolder = new ServletHolder(restServer);
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
@ -184,10 +209,77 @@ public class CorsTest {
} }
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
@AfterClass @Create
public static void afterClassClearContext() { public MethodOutcome create(@ResourceParam Patient thePatient) {
TestUtil.clearAllStaticFieldsForUnitTest(); return new MethodOutcome(thePatient.getIdElement());
} }
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
Patient patient = new Patient();
patient.setId("1");
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUse.OFFICIAL);
patient.getIdentifier().get(0).setSystem(("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.setGender(AdministrativeGender.MALE);
idToPatient.put("1", patient);
}
{
Patient patient = new Patient();
patient.setId("2");
patient.getIdentifier().add(new Identifier());
patient.getIdentifier().get(0).setUse(IdentifierUse.OFFICIAL);
patient.getIdentifier().get(0).setSystem(("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00002");
patient.getName().add(new HumanName());
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.setGender(AdministrativeGender.FEMALE);
idToPatient.put("2", patient);
}
return idToPatient;
}
@Search()
public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
for (Patient next : getIdToPatient().values()) {
for (Identifier nextId : next.getIdentifier()) {
if (nextId.getSystem().equals(theIdentifier.getSystem())&& nextId.getValue().equals(theIdentifier.getValue())) {
return next;
}
}
}
return null;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Read()
public Patient getResourceById(@IdParam IdType theId) {
return getIdToPatient().get(theId.getValue());
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
}
} }