diff --git a/pom.xml b/pom.xml
index ab36cc1..2fb59a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -372,13 +372,11 @@
org.junit.jupiter
junit-jupiter-api
- 5.7.2
test
org.junit.jupiter
junit-jupiter-engine
- 5.7.2
test
diff --git a/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthBase.java b/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthBase.java
index 5e57a25..4b1aeb0 100644
--- a/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthBase.java
+++ b/src/main/java/edu/ohsu/cmp/ecp/sds/SupplementalDataStoreAuthBase.java
@@ -1,5 +1,7 @@
package edu.ohsu.cmp.ecp.sds;
+import java.util.function.Consumer;
+
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -80,8 +82,14 @@ private LaunchContext launchContextFromAuthentication(Authentication authenticat
if (null == contextPatient)
return null;
-
- IIdType contextPatientId = idFromContextParameter( contextPatient.toString() ).withResourceType( "Patient" ) ;
+ IIdType contextPatientId =
+ coerceToResourceType(
+ idFromContextParameter( contextPatient.toString() ),
+ "Patient",
+ (actualResourceType) -> {
+ throw new AuthenticationException(Msg.code(644) + "Launch Context Patient \"" + contextPatient + "\" must be the id of a patient, but found a resource type of \"" + actualResourceType + "\"");
+ }
+ ) ;
IIdType fullyQualifiedContextPatientId = fullyQualifiedContextPatientId( contextPatientId, oauth2Principal );
return new LaunchContext() {
@@ -94,6 +102,20 @@ public IIdType getPatient() {
};
}
+ private static IIdType coerceToResourceType( IIdType id, String resourceType, Consumer onResourceTypeMismatch ) {
+ if ( id.hasResourceType() ) {
+ if ( null != onResourceTypeMismatch && !resourceType.equals( id.getResourceType() ) ) {
+ onResourceTypeMismatch.accept( id.getResourceType() ) ;
+ }
+ }
+
+ if ( id.hasBaseUrl() ) {
+ return id.withServerBase( id.getBaseUrl(), resourceType ) ;
+ } else {
+ return id.withResourceType( resourceType ) ;
+ }
+ }
+
private IIdType fullyQualifiedContextPatientId( IIdType contextPatientId, OAuth2AuthenticatedPrincipal oauth2Principal ) {
if ( contextPatientId.hasBaseUrl() )
return contextPatientId ;
diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/AppClientIntrospectTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/AppClientIntrospectTest.java
new file mode 100644
index 0000000..e3ae95e
--- /dev/null
+++ b/src/test/java/edu/ohsu/cmp/ecp/sds/AppClientIntrospectTest.java
@@ -0,0 +1,595 @@
+package edu.ohsu.cmp.ecp.sds;
+
+import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesSameResourceAs;
+import static edu.ohsu.cmp.ecp.sds.SupplementalDataStorePermissionsInterceptor.getPermissions;
+import static java.util.stream.Collectors.joining;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockserver.model.HttpRequest.request;
+import static org.mockserver.model.HttpResponse.response;
+import static org.mockserver.model.JsonBody.json;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.IdType;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockserver.client.MockServerClient;
+import org.mockserver.mock.Expectation;
+import org.mockserver.model.HttpRequest;
+import org.mockserver.model.HttpResponse;
+import org.mockserver.model.RequestDefinition;
+import org.mockserver.springtest.MockServerPort;
+import org.mockserver.springtest.MockServerTest;
+import org.opentest4j.AssertionFailedError;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.TestPropertySource;
+
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
+import edu.ohsu.cmp.ecp.sds.Permissions.ReadAllPatients;
+import edu.ohsu.cmp.ecp.sds.Permissions.ReadAndWriteSpecificPatient;
+import edu.ohsu.cmp.ecp.sds.Permissions.ReadSpecificPatient;
+
+@MockServerTest
+@ActiveProfiles( { "auth-aware-test", "http-aware-test" } )
+@TestPropertySource(properties = {
+ "spring.security.oauth2.resourceserver.opaque-token.introspection-uri=http://localhost:${mockServerPort}/oauth2/introspect"
+})
+public class AppClientIntrospectTest extends BaseSuppplementalDataStoreTest {
+
+ private MockServerClient mockServerClient ;
+
+ @MockServerPort
+ Integer mockServerPort;
+
+ @MockBean
+ SupplementalDataStoreAuthorizationInterceptor supplementalDataStoreAuthorizationInterceptor ;
+
+ private final List authInterceptorPermissions = new ArrayList<>() ; ;
+
+ private Permissions latestPermissions() {
+ if ( authInterceptorPermissions.isEmpty() )
+ return null ;
+ return authInterceptorPermissions.get(0);
+ }
+
+ private URL ehrBaseUrl ;
+ private String token ;
+
+ @BeforeEach
+ public void setupServerUrlAndMetadataResponse() throws MalformedURLException {
+ ehrBaseUrl = new URL( "http", "localhost", mockServerPort, "/fhir/R4" );
+ mockServerClient.when( metadataRequest() ).respond( metadataResponse() );
+ }
+
+ @BeforeEach
+ public void setupAuthToken() {
+ token = createTestSpecificId() ;
+ }
+
+ @BeforeEach
+ public void setupAuthCapture() {
+ /* ArgumentCaptor is not useful here because the underlying servlet request is cleared before the HTTP request finishes */
+ authInterceptorPermissions.clear() ;
+ doAnswer( inv -> {
+ RequestDetails rq = inv.getArgument(0, RequestDetails.class) ;
+ Permissions p = getPermissions( rq ) ;
+ authInterceptorPermissions.add( p ) ;
+ return null ; /* void response ; no throw for DENY */
+ } )
+ .when( supplementalDataStoreAuthorizationInterceptor ).incomingRequestPreHandled( any(RequestDetails.class), any(Pointcut.class) ) ;
+ }
+
+ private Expectation respondToIntrospectWith( HttpResponse response ) {
+ Expectation[] expectations =
+ mockServerClient
+ .when( oauth2IntrospectRequest() )
+ .respond( response )
+ ;
+ return expectations[0] ;
+ }
+
+ private Expectation respondToRelatedPersonRequestWith( IIdType relatedPersonId, HttpResponse response ) {
+ Expectation[] expectations =
+ mockServerClient
+ .when( relatedPersonRequest(relatedPersonId) )
+ .respond( response )
+ ;
+ return expectations[0] ;
+ }
+
+ private void verifyExpectations( Expectation ... expectations) {
+ String[] expectationIds = Arrays.stream(expectations).map( Expectation::getId ).toArray( String[]::new ) ;
+ mockServerClient.verify( expectationIds ) ;
+ }
+
+ private void assertAuthorizationToReadResourceThatisMissing( IIdType resourceId, boolean expectAuthorized ) {
+ IGenericClient patientAppClient = authenticatingClient( token ) ;
+
+ if ( expectAuthorized ) {
+
+ assertThrows( ResourceNotFoundException.class, () -> {
+ patientAppClient.read().resource( resourceId.getResourceType() ).withId( resourceId.getIdPart() ).execute();
+ });
+
+ } else {
+
+ assertThrows( AuthenticationException.class, () -> {
+ patientAppClient.read().resource( resourceId.getResourceType() ).withId( resourceId.getIdPart() ).execute();
+ });
+
+ }
+ }
+
+ private interface Configurator extends Function {} ;
+
+ private static final Configurator WITH_BASE_URL_WITHOUT_FHIR_USER = (builder) -> {
+ return builder ;
+ };
+
+ private static final Configurator WITH_BASE_URL_WITH_FHIR_USER = (builder) -> {
+ return builder.withFhirUser() ;
+ };
+
+ private static final Configurator WITHOUT_BASE_URL_WITHOUT_FHIR_USER = (builder) -> {
+ return builder.withoutBaseUrl() ;
+ };
+
+ private static final Configurator WITHOUT_BASE_URL_WITH_FHIR_USER = (builder) -> {
+ return builder.withoutBaseUrl().withFhirUser() ;
+ };
+
+ private static final Configurator WITHOUT_RESOURCE_TYPE_WITHOUT_FHIR_USER = (builder) -> {
+ return builder.withoutResourceType() ;
+ };
+
+ private static final Configurator WITHOUT_RESOURCE_TYPE_WITH_FHIR_USER = (builder) -> {
+ return builder.withoutResourceType().withFhirUser() ;
+ };
+
+ /************************************************************************************************************
+ * canPermitPatientReadWriteAccessWhenAuthorizationIsPatient (various introspect responses)
+ ************************************************************************************************************/
+
+ private void checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( Configurator configurator, boolean expectAuthorized ) {
+ IIdType authorizedPatientId = new IdType( ehrBaseUrl.toString(), "Patient", createTestSpecificId(), null ) ;
+
+ Expectation oauth2Expectation =
+ respondToIntrospectWith(
+ configurator.apply(
+ introspectResponseBuilder().patient(authorizedPatientId)
+ )
+ .build()
+ );
+
+ assertAuthorizationToReadResourceThatisMissing( authorizedPatientId, expectAuthorized ) ;
+
+ verifyExpectations( oauth2Expectation ) ;
+
+ if ( expectAuthorized ) {
+
+ Permissions permissions = latestPermissions();
+
+ assertThat( permissions, notNullValue() ) ;
+ ReadAndWriteSpecificPatient readAndWriteSpecificPatient =
+ permissions.readAndWriteSpecificPatient()
+ .orElseThrow( AssertionFailedError::new );
+ assertThat( readAndWriteSpecificPatient.authorizedUserId(), identifiesSameResourceAs( authorizedPatientId ) ) ;
+ assertThat( readAndWriteSpecificPatient.patientId().basisUserId(), identifiesSameResourceAs( authorizedPatientId ) ) ;
+
+ }
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsPatient_Config001() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( WITH_BASE_URL_WITHOUT_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsPatient_Config002() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( WITH_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitPatientReadWriteAccessWhenAuthorizationIsPatient_Config003() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( WITHOUT_BASE_URL_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsPatient_Config004() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( WITHOUT_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitPatientReadWriteAccessWhenAuthorizationIsPatient_Config005() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( WITHOUT_RESOURCE_TYPE_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsPatient_Config006() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsPatient( WITHOUT_RESOURCE_TYPE_WITH_FHIR_USER, true ) ;
+ }
+
+
+ /************************************************************************************************************
+ * checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext (various introspect responses)
+ ************************************************************************************************************/
+
+ private void checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( Configurator configurator, boolean expectPermitted ) {
+ IIdType authorizedPractitionerId = new IdType( ehrBaseUrl.toString(), "Practitioner", createTestSpecificId(), null ) ;
+ IIdType contextPatientId = new IdType( ehrBaseUrl.toString(), "Patient", createTestSpecificId(), null ) ;
+
+ Expectation oauth2Expectation =
+ respondToIntrospectWith(
+ configurator.apply(
+ introspectResponseBuilder().practitioner(authorizedPractitionerId).context(contextPatientId)
+ )
+ .build()
+ );
+
+ assertAuthorizationToReadResourceThatisMissing( contextPatientId, expectPermitted ) ;
+
+ verifyExpectations( oauth2Expectation ) ;
+
+ if ( expectPermitted ) {
+
+ Permissions permissions = latestPermissions();
+
+ assertThat( permissions, notNullValue() ) ;
+ ReadSpecificPatient readSpecificPatient =
+ permissions.readSpecificPatient()
+ .orElseThrow( AssertionFailedError::new );
+ assertThat( readSpecificPatient.authorizedUserId(), identifiesSameResourceAs( authorizedPractitionerId ) ) ;
+ assertThat( readSpecificPatient.patientId().basisUserId(), identifiesSameResourceAs( contextPatientId ) ) ;
+
+ }
+ }
+
+ @Test
+ public void canPermitPatientReadAccessWhenAuthorizationIsProviderInPatientContext_Config001() {
+ checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( WITH_BASE_URL_WITHOUT_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadAccessWhenAuthorizationIsProviderInPatientContext_Config002() {
+ checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( WITH_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitPatientReadAccessWhenAuthorizationIsProviderInPatientContext_Config003() {
+ checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( WITHOUT_BASE_URL_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadAccessWhenAuthorizationIsProviderInPatientContext_Config004() {
+ checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( WITHOUT_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitPatientReadAccessWhenAuthorizationIsProviderInPatientContext_Config005() {
+ checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( WITHOUT_RESOURCE_TYPE_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadAccessWhenAuthorizationIsProviderInPatientContext_Config006() {
+ checkPermissionForPatientReadAccessWhenAuthorizationIsProviderInPatientContext( WITHOUT_RESOURCE_TYPE_WITH_FHIR_USER, true ) ;
+ }
+
+ /************************************************************************************************************
+ * checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider (various introspect responses)
+ ************************************************************************************************************/
+
+ private void checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( Configurator configurator, boolean expectAuthorized ) {
+ IIdType authorizedPractitionerId = new IdType( ehrBaseUrl.toString(), "Practitioner", createTestSpecificId(), null ) ;
+
+ Expectation oauth2Expectation =
+ respondToIntrospectWith(
+ configurator.apply(
+ introspectResponseBuilder().practitioner(authorizedPractitionerId)
+ )
+ .build()
+ );
+
+ IdType samplePatientId = new IdType( ehrBaseUrl.toString(), "Patient", createTestSpecificId(), null );
+
+ assertAuthorizationToReadResourceThatisMissing( samplePatientId, expectAuthorized ) ;
+
+ verifyExpectations( oauth2Expectation ) ;
+
+ if ( expectAuthorized ) {
+
+ Permissions permissions = latestPermissions();
+
+ assertThat( permissions, notNullValue() ) ;
+ ReadAllPatients readAllPatients =
+ permissions.readAllPatients()
+ .orElseThrow( AssertionFailedError::new );
+ assertThat( readAllPatients.authorizedUserId(), identifiesSameResourceAs( authorizedPractitionerId ) ) ;
+
+ }
+ }
+
+ @Test
+ public void canPermitAllPatientsReadAccessWhenAuthorizationIsProvider_Config001() {
+ checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( WITH_BASE_URL_WITHOUT_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void canPermitAllPatientsReadAccessWhenAuthorizationIsProvider_Config002() {
+ checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( WITH_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitAllPatientsReadAccessWhenAuthorizationIsProvider_Config003() {
+ checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( WITHOUT_BASE_URL_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitAllPatientsReadAccessWhenAuthorizationIsProvider_Config004() {
+ checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( WITHOUT_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitAllPatientsReadAccessWhenAuthorizationIsProvider_Config005() {
+ checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( WITHOUT_RESOURCE_TYPE_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitAllPatientsReadAccessWhenAuthorizationIsProvider_Config006() {
+ checkPermissionForAllPatientsReadAccessWhenAuthorizationIsProvider( WITHOUT_RESOURCE_TYPE_WITH_FHIR_USER, true ) ;
+ }
+
+ /************************************************************************************************************
+ * checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson (various introspect responses)
+ ************************************************************************************************************/
+
+ private void checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( Configurator configurator, boolean expectAuthorized ) {
+ IIdType authorizedRelatedPersonId = new IdType( ehrBaseUrl.toString(), "RelatedPerson", createTestSpecificId(), null ) ;
+ IIdType authorizedPatientId = new IdType( ehrBaseUrl.toString(), "Patient", createTestSpecificId(), null ) ;
+
+ Expectation oauth2Expectation =
+ respondToIntrospectWith(
+ configurator.apply(
+ introspectResponseBuilder().relatedPerson(authorizedRelatedPersonId)
+ )
+ .build()
+ );
+
+ Expectation relatedPersonExpectation =
+ respondToRelatedPersonRequestWith(
+ authorizedRelatedPersonId,
+ relatedPersonResponse(authorizedRelatedPersonId, authorizedPatientId)
+ );
+
+ assertAuthorizationToReadResourceThatisMissing( authorizedPatientId, expectAuthorized ) ;
+
+ verifyExpectations( oauth2Expectation ) ;
+
+ if ( expectAuthorized ) {
+
+ verifyExpectations( relatedPersonExpectation ) ;
+
+ Permissions permissions = latestPermissions();
+
+ assertThat( permissions, notNullValue() ) ;
+ ReadAndWriteSpecificPatient readAndWriteSpecificPatient =
+ permissions.readAndWriteSpecificPatient()
+ .orElseThrow( AssertionFailedError::new );
+ assertThat( readAndWriteSpecificPatient.authorizedUserId(), identifiesSameResourceAs( authorizedRelatedPersonId ) ) ;
+ assertThat( readAndWriteSpecificPatient.patientId().basisUserId(), identifiesSameResourceAs( authorizedPatientId ) ) ;
+ }
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsRelatedPerson_Config001() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( WITH_BASE_URL_WITHOUT_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsRelatedPerson_Config002() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( WITH_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitPatientReadWriteAccessWhenAuthorizationIsRelatedPerson_Config003() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( WITHOUT_BASE_URL_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsRelatedPerson_Config004() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( WITHOUT_BASE_URL_WITH_FHIR_USER, true ) ;
+ }
+
+ @Test
+ public void cannotPermitPatientReadWriteAccessWhenAuthorizationIsRelatedPerson_Config005() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( WITHOUT_RESOURCE_TYPE_WITHOUT_FHIR_USER, false ) ;
+ }
+
+ @Test
+ public void canPermitPatientReadWriteAccessWhenAuthorizationIsRelatedPerson_Config006() {
+ checkPermissionForPatientReadWriteAccessWhenAuthorizationIsRelatedPerson( WITHOUT_RESOURCE_TYPE_WITH_FHIR_USER, true ) ;
+ }
+
+ /************************************************************************************************************
+ * request and response helpers
+ ************************************************************************************************************/
+
+ private HttpRequest metadataRequest( ) {
+ return request()
+ .withMethod("GET")
+ .withPath( ehrBaseUrl.getPath() + "/metadata")
+ ;
+ }
+
+ private HttpResponse metadataResponse() {
+ String jsonBody =
+ "{ \"resourceType\": \"CapabilityStatement\" }"
+ ;
+ return response()
+ .withStatusCode( 200 )
+ .withBody( json( jsonBody ) )
+ ;
+
+ }
+
+ private RequestDefinition oauth2IntrospectRequest() {
+ return request()
+ .withMethod( "POST" )
+ .withPath( "/oauth2/introspect" )
+ .withHeader( "Authorization", "Bearer " + token )
+ .withBody( "token=" + token )
+ ;
+ }
+
+ private interface IntrospectResponseBuilder {
+ IntrospectResponseBuilder patient( IIdType userId ) ;
+ IntrospectResponseBuilder practitioner( IIdType userId ) ;
+ IntrospectResponseBuilder context( IIdType context ) ;
+ IntrospectResponseBuilder relatedPerson( IIdType userId ) ;
+ IntrospectResponseBuilder withoutBaseUrl() ;
+ IntrospectResponseBuilder withoutResourceType() ;
+ IntrospectResponseBuilder withFhirUser() ;
+ HttpResponse build() ;
+ }
+
+ IntrospectResponseBuilder introspectResponseBuilder() {
+ return new IntrospectResponseBuilder() {
+ private IIdType subject ;
+ private IIdType context ;
+ private boolean includeBaseUrlInSubject = true ;
+ private boolean includeResourceTypeInSubject = true;
+ private boolean includeFhirUserFromSubject = false ;
+
+ public IntrospectResponseBuilder patient( IIdType patientId ) {
+ if ( !"Patient".equals(patientId.getResourceType()) )
+ throw new AssertionFailedError("IntrospectResponseBuilder(...) expected a user of type \"Patient\" but received \"" + patientId.getResourceType() + "\"" ) ;
+ this.subject = patientId ;
+ return this ;
+ }
+
+ public IntrospectResponseBuilder practitioner( IIdType practitionerId ) {
+ if ( !"Practitioner".equals(practitionerId.getResourceType()) )
+ throw new AssertionFailedError("IntrospectResponseBuilder(...) expected a user of type \"Practitioner\" but received \"" + practitionerId.getResourceType() + "\"" ) ;
+ this.subject = practitionerId ;
+ return this ;
+ }
+
+ public IntrospectResponseBuilder context( IIdType context ) {
+ if ( !"Patient".equals(context.getResourceType()) )
+ throw new AssertionFailedError("IntrospectResponseBuilder(...) expected a user of type \"Patient\" but received \"" + context.getResourceType() + "\"" ) ;
+ this.context = context ;
+ return this ;
+ }
+
+ public IntrospectResponseBuilder relatedPerson( IIdType relatedPersonId ) {
+ if ( !"RelatedPerson".equals(relatedPersonId.getResourceType()) )
+ throw new AssertionFailedError("IntrospectResponseBuilder(...) expected a user of type \"RelatedPerson\" but received \"" + relatedPersonId.getResourceType() + "\"" ) ;
+ this.subject = relatedPersonId ;
+ return this ;
+ }
+
+ public IntrospectResponseBuilder withoutBaseUrl() {
+ this.includeBaseUrlInSubject = false ;
+ return this ;
+ }
+
+ public IntrospectResponseBuilder withoutResourceType() {
+ this.includeBaseUrlInSubject = false ;
+ this.includeResourceTypeInSubject = false ;
+ return this ;
+ }
+
+ public IntrospectResponseBuilder withFhirUser() {
+ this.includeFhirUserFromSubject = true ;
+ return this ;
+ }
+
+ private String initialToLowerCase( String s ) {
+ if ( s.isEmpty() )
+ return s ;
+ String initial = s.substring(0, 1);
+ if ( s.length() < 2 )
+ return initial ;
+ String remaining = s.substring( 1 );
+ return initial.toLowerCase() + remaining ;
+ }
+
+ public HttpResponse build() {
+ if ( null == subject )
+ throw new AssertionFailedError("IntrospectResponseBuilder(...) expected a subject" ) ;
+
+ if ( null != context && !"Practitioner".equals(subject.getResourceType()) )
+ throw new AssertionFailedError("IntrospectResponseBuilder(...) expected a subject of type \"Practitioner\" when a context is present but received \"" + subject.getResourceType() + "\"" ) ;
+
+ Map fields = new HashMap<>() ;
+
+ fields.put( "active", true ) ;
+ fields.put( "exp", Long.toString( System.currentTimeMillis() + 60000 ) );
+
+ if ( includeFhirUserFromSubject )
+ fields.put( "fhirUser", "\"" + subject + "\"" ) ;
+
+ if ( includeBaseUrlInSubject )
+ fields.put( "sub", "\"" + subject + "\"" ) ;
+ else if ( includeResourceTypeInSubject )
+ fields.put( "sub", "\"" + subject.toUnqualifiedVersionless() + "\"" ) ;
+ else
+ fields.put( "sub", "\"" + subject.getIdPart() + "\"" ) ;
+
+ if ( null != context )
+ fields.put( initialToLowerCase(context.getResourceType()), "\"" + context + "\"" ) ;
+
+ String jsonBody = fields.entrySet().stream().map( e -> "\"" + e.getKey() + "\": " + e.getValue() ).collect( joining(", ", "{ ", " }") ) ;
+ return response()
+ .withStatusCode( 200 )
+ .withBody( json( jsonBody ) )
+ ;
+ }
+ } ;
+ }
+
+ private RequestDefinition relatedPersonRequest( IIdType relatedPersonId ) {
+ if ( !"RelatedPerson".equals( relatedPersonId.getResourceType() ) )
+ throw new AssertionFailedError("relatedPersonRequest(...) expected a related person of type \"RelatedPerson\" but encountered \"" + relatedPersonId.getResourceType() + "\"" ) ;
+ return request()
+ .withMethod( "GET" )
+ .withPath( ehrBaseUrl.getPath()+ "/" + relatedPersonId.getResourceType() + "/" + relatedPersonId.getIdPart() )
+ .withHeader( "Authorization", "Bearer " + token )
+ ;
+ }
+
+ private HttpResponse relatedPersonResponse( IIdType relatedPersonId, IIdType patientId ) {
+ if ( !"RelatedPerson".equals( relatedPersonId.getResourceType() ) )
+ throw new AssertionFailedError("relatedPersonResponse(...) expected a related person of type \"RelatedPerson\" but encountered \"" + relatedPersonId.getResourceType() + "\"" ) ;
+ if ( !"Patient".equals( patientId.getResourceType() ) )
+ throw new AssertionFailedError("relatedPersonResponse(...) expected a patient of type \"Patient\" but encountered \"" + patientId.getResourceType() + "\"" ) ;
+ String jsonBody =
+ String.format(
+ "{ \"resourceType\": \"RelatedPerson\", \"id\": \"%2$s\", \"patient\": { \"reference\": \"%1$s\" } }",
+ patientId,
+ relatedPersonId
+ );
+ return response()
+ .withStatusCode( 200 )
+ .withBody( json( jsonBody ) )
+ ;
+
+ }
+
+}
diff --git a/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java b/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java
index f738b3a..9653ab8 100644
--- a/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java
+++ b/src/test/java/edu/ohsu/cmp/ecp/sds/BaseSuppplementalDataStoreTest.java
@@ -53,7 +53,7 @@
JpaStarterWebsocketDispatcherConfig.class
},
properties = {
- "spring.datasource.url=jdbc:h2:mem:dbr4",
+ "spring.datasource.url=jdbc:h2:mem:dbr4;DB_CLOSE_DELAY=-1",
"hapi.fhir.fhir_version=r4"
}
)