Skip to content

Commit

Permalink
support authorized patient ids that include base url
Browse files Browse the repository at this point in the history
  • Loading branch information
timcoffman committed Apr 19, 2024
1 parent 8210ae2 commit 223f673
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
if ( permissions.readAndWriteSpecificPatient().isPresent() )
ruleBuilder = buildRuleListForPermissions( ruleBuilder, permissions.readAndWriteSpecificPatient().get() ) ;

return ruleBuilder.denyAll("everything else").build();
return ruleBuilder.denyAll("no access rules grant permission").build();
}

private IAuthRuleBuilder buildRuleListForPermissions( IAuthRuleBuilder ruleBuilder, Permissions.ReadAllPatients readAllPatients ) {
Expand Down Expand Up @@ -106,7 +106,12 @@ private IAuthRuleBuilder buildRuleListForPermissions( IAuthRuleBuilder ruleBuild
.allResources()
.inCompartment("Patient", localPatientId)
.andThen()
.allow("write local patient " + localPatientId)
.deny( "write local patient " + localPatientId )
.write()
.resourcesOfType( "Patient" )
.inCompartment("Patient", localPatientId)
.andThen()
.allow("write local patient " + localPatientId + " related resources")
.write()
.allResources()
.inCompartment("Patient", localPatientId)
Expand Down Expand Up @@ -137,6 +142,22 @@ private IAuthRuleBuilder buildRuleListForPermissions( IAuthRuleBuilder ruleBuild
.andThen()
;

if ( nonLocalPatientId.hasBaseUrl() ) {
IIdType nonLocalPatientIdWithoutBaseUrl = nonLocalPatientId.toUnqualified();
ruleBuilder = ruleBuilder
.allow("read non-local patient " + nonLocalPatientIdWithoutBaseUrl )
.read()
.allResources()
.inCompartment("Patient", nonLocalPatientIdWithoutBaseUrl)
.andThen()
.allow("write non-local patient " + nonLocalPatientIdWithoutBaseUrl)
.write()
.allResources()
.inCompartment("Patient", nonLocalPatientIdWithoutBaseUrl)
.andThen()
;
}

/* permit access to all sds-local linkages that link to specific patient */
ruleBuilder = ruleBuilder
.allow("read linkages for non-local patient " + nonLocalPatientId)
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ hapi:
# allowed_bundle_types: COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET
# allow_cascading_deletes: true
# allow_contains_searches: true
# allow_external_references: true
allow_external_references: true
# allow_multiple_delete: true
# allow_override_default_search_params: true
# auto_create_placeholder_reference_targets: false
Expand Down
18 changes: 18 additions & 0 deletions src/test/java/edu/ohsu/cmp/ecp/sds/LocalPartitionTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package edu.ohsu.cmp.ecp.sds;

import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
Expand Down Expand Up @@ -32,4 +33,21 @@ void canStoreAndRetrieveResourceInLocalPartition() {
Assertions.assertNotNull( readQuestResp );

}

@Test
void canStoreAndRetrieveConditionResourceInLocalPartition() {
IGenericClient client = client() ;

Patient pat = new Patient();
IIdType patId = client.create().resource(pat).execute().getId();

Condition condition = new Condition() ;
condition.setSubject( new Reference(patId) );
IIdType conditionId = client.create().resource(condition).execute().getId();

Condition readCondition = client.read().resource(Condition.class).withId(conditionId).execute();

Assertions.assertNotNull( readCondition );

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package edu.ohsu.cmp.ecp.sds;

import static java.util.Arrays.asList;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesResource;
import static edu.ohsu.cmp.ecp.sds.SupplementalDataStoreMatchers.identifiesSameResourceAs;

import java.util.List;

import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Linkage;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.UriType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;

import ca.uhn.fhir.jpa.starter.AppTestMockPermissionRegistry;
import ca.uhn.fhir.jpa.starter.AppTestMockPrincipalRegistry;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import junit.framework.AssertionFailedError;

@ActiveProfiles( "auth-aware-test")
public class PatientAppClientForeignPartitionTest extends BaseSuppplementalDataStoreTest {

/*
* Use Case: store NON-LOCAL Patient and other resources in the patient compartment
*/

@Autowired
AppTestMockPrincipalRegistry mockPrincipalRegistry ;

@Autowired
AppTestMockPermissionRegistry mockPermissionRegistry ;

private static final String FOREIGN_PARTITION_NAME = "http://my.ehr.org/fhir/R4/" ;

private IIdType authorizedPatientId;
private IGenericClient patientAppClient ;

@BeforeEach
public void setupAuthorizedPatient() {
authorizedPatientId = new IdType( FOREIGN_PARTITION_NAME, "Patient", createTestSpecificId(), null );
String token = mockPrincipalRegistry.register().principal( "MyPatient", authorizedPatientId.toString() ).token() ;

patientAppClient = authenticatingClientTargetingPartition( token, FOREIGN_PARTITION_NAME ) ;
}

@Test
void canStoreAndReadPatientThatIsAuthorizedPatient() {
Patient patient = new Patient() ;
patient.setId( new IdType( "Patient", authorizedPatientId.getIdPart() ) ) ;
IIdType patientId = patientAppClient.update().resource(patient).execute().getId();

assertThat( patientId, identifiesResource( patient ) );

Patient readPatient = patientAppClient.read().resource(Patient.class).withId( patientId ).execute();

assertThat( readPatient, notNullValue() );
assertThat( readPatient.getIdElement(), identifiesSameResourceAs( patient.getIdElement() ) );
}

@Test
void canStorePatientWithoutAdditionalSetup() {
String subjectPatientId = createTestSpecificId() ;
Patient subjectPatient = new Patient() ;
subjectPatient.setId( new IdType( "Patient", subjectPatientId ) ) ;

IIdType claimedPatientId = patientAppClient.update().resource(subjectPatient).execute().getId();

Patient readPatientResp = patientAppClient.read().resource(Patient.class).withId(claimedPatientId).execute();

Assertions.assertNotNull( readPatientResp );
}

private Condition createHealthConcern( Reference subjectRef, String codeAsPlainText ) {
Condition condition = new Condition() ;
condition.setSubject( subjectRef ) ;

CodeableConcept healthConcernCategory = new CodeableConcept();
healthConcernCategory.addCoding( new Coding( "http://hl7.org/fhir/ValueSet/condition-category", "health-concern", "health concern" ) ) ;
condition.setCategory( asList( healthConcernCategory ) ) ;

condition.setCode( healthConcernCategory.setText(codeAsPlainText) ) ;

return condition ;
}

@Test
void canStoreConditionWhereSubjectIsClaimedPatientWithoutAdditionalSetup() {
String subjectPatientId = createTestSpecificId() ;
Patient subjectPatient = new Patient() ;
subjectPatient.setId( new IdType( "Patient", subjectPatientId ) ) ;

IIdType claimedPatientId = patientAppClient.update().resource(subjectPatient).execute().getId();

Condition condition = createHealthConcern( new Reference(claimedPatientId), "my health concern" ) ;

IIdType conditionId = patientAppClient.create().resource(condition).execute().getId();

Condition readQuestResp = patientAppClient.read().resource(Condition.class).withId(conditionId).execute();

Assertions.assertNotNull( readQuestResp );
}

@Test
void canExpunge() {
String authorizedPatientId = createTestSpecificId() ;
String token = mockPrincipalRegistry.register().principal( "MyPatient", "Patient/" + authorizedPatientId ).token() ;

IGenericClient patientAppClient = authenticatingClient( token ) ;

Reference authorizedPatient = new Reference( new IdType( "Patient", authorizedPatientId ) );

List<Linkage> linkages = new TestClientSearch(patientAppClient).searchLinkagesWhereItemRefersTo( authorizedPatient.getReferenceElement() ) ;
assertThat( linkages, hasSize(1) ) ;
Linkage linkage = linkages.get(0) ;
Reference localPatientRef =
linkage.getItem().stream()
.filter( i -> i.getType() == Linkage.LinkageType.SOURCE )
.map( Linkage.LinkageItemComponent::getResource )
.findFirst()
.orElseThrow( AssertionFailedError::new )
;

String questId = createTestSpecificId();

QuestionnaireResponse questionnaireResponse = new QuestionnaireResponse() ;
questionnaireResponse.setSubject( authorizedPatient ) ;
questionnaireResponse.setQuestionnaire( questId ) ;
IIdType questRespId = patientAppClient.create().resource(questionnaireResponse).execute().getId();

QuestionnaireResponse readQuestResp = patientAppClient.read().resource(QuestionnaireResponse.class).withId(questRespId).execute();
Assertions.assertNotNull( readQuestResp );

// patientAppClient
// .operation()
// .onInstance( localPatientRef.getReferenceElement() )
// .named( "$expunge" )
// .withParameter( BooleanType.class, "expungeEverything", new BooleanType(true) )
// ;
}

}
Loading

0 comments on commit 223f673

Please sign in to comment.