make consent service dont call willSeeResource on children if parent resource is AUTHORIZED or REJECT (#6127)

This commit is contained in:
Emre Dincturk 2024-07-23 16:14:11 -04:00 committed by GitHub
parent 55734e6cef
commit 9860511d44
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 318 additions and 40 deletions

View File

@ -0,0 +1,7 @@
---
type: fix
issue: 6124
title: "Previously, when retrieving a resource which may contain other resources, such as a document Bundle,
if a ConsentService's willSeeResource returned AUTHORIZED or REJECT on this parent resource, the willSeeResource was
still being called for the child resources. This has now been fixed so that if a consent service
returns AUTHORIZED or REJECT for a parent resource, willSeeResource is not called for the child resources."

View File

@ -1,7 +1,5 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
@ -11,6 +9,7 @@ import ca.uhn.fhir.jpa.entity.Search;
import ca.uhn.fhir.jpa.model.search.SearchStatusEnum;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
@ -46,6 +45,7 @@ import org.apache.http.entity.StringEntity;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.IdType;
@ -71,11 +71,12 @@ import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.leftPad;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.fail;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -630,6 +631,73 @@ public class ConsentInterceptorResourceProviderR4IT extends BaseResourceProvider
}
@Test
void testGetBundle_WhenWillSeeReturnsRejectForABundle_ReadingBundleThrowsResourceNotFound() {
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcWillSeeRejectsBundlesAuthorizesOthers());
myServer.getRestfulServer().
getInterceptorService().registerInterceptor(myConsentInterceptor);
// create bundle
Bundle bundle = createDocumentBundle();
MethodOutcome createOutcome = myClient.create().resource(bundle).execute();
IIdType bundleId = createOutcome.getResource().getIdElement();
// read the created bundle back
ResourceNotFoundException ex = assertThrows(ResourceNotFoundException.class,
() -> myClient.read().resource(Bundle.class).withId(bundleId).execute());
assertEquals(404, ex.getStatusCode());
}
@Test
void testGetBundle_WhenWillSeeReturnsAuthorizedForABundle_ChildResourcesInTheBundleAreVisible() {
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcWillSeeAuthorizesBundlesRejectsOthers());
myServer.getRestfulServer().
getInterceptorService().registerInterceptor(myConsentInterceptor);
// create bundle
Bundle bundle = createDocumentBundle();
MethodOutcome createOutcome = myClient.create().resource(bundle).execute();
IIdType bundleId = createOutcome.getResource().getIdElement();
// read the created bundle back
Bundle bundleRead = myClient.read().resource(Bundle.class).withId(bundleId).execute();
// since the consent service AUTHORIZED the bundle, the child resources in the bundle should be visible
// because willSeeResource won't be called for the child resources once the bundle is AUTHORIZED
assertEquals(2, bundleRead.getEntry().size());
Composition compositionEntry = (Composition) bundleRead.getEntry().get(0).getResource();
assertEquals("Composition/composition-in-bundle", compositionEntry.getId());
Patient patientEntry = (Patient) bundleRead.getEntry().get(1).getResource();
assertEquals("Patient/patient-in-bundle", patientEntry.getId());
}
@Test
void testGetBundle_WhenWillSeeReturnsProceedForABundle_WillSeeIsCalledForChildResourcesInTheBundle() {
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcWillSeeProceedsBundlesRejectsOthers());
myServer.getRestfulServer().
getInterceptorService().registerInterceptor(myConsentInterceptor);
// create a bundle
Bundle bundle = createDocumentBundle();
MethodOutcome createOutcome = myClient.create().resource(bundle).execute();
IIdType bundleId = createOutcome.getResource().getIdElement();
//read the created bundle back
Bundle bundleRead = myClient.read().resource(Bundle.class).withId(bundleId).execute();
// since the consent service replies with PROCEED for the bundle in this test case,
// willSeeResource should be called for the child resources in the bundle and would be rejected by the
// consent service, so the child resources in the bundle should not be visible
assertEquals(0, bundleRead.getEntry().size());
}
/**
* Make sure the default methods all work and allow the response to proceed
*/
@ -736,7 +804,7 @@ public class ConsentInterceptorResourceProviderR4IT extends BaseResourceProvider
// given
create50Observations();
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcRejectWillSeeResource());
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcWillSeeProceedsBundlesRejectsOthers());
myServer.getRestfulServer().getInterceptorService().registerInterceptor(myConsentInterceptor);
// when
@ -754,7 +822,7 @@ public class ConsentInterceptorResourceProviderR4IT extends BaseResourceProvider
// given
create50Observations();
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcRejectWillSeeResource());
myConsentInterceptor = new ConsentInterceptor(new ConsentSvcWillSeeProceedsBundlesRejectsOthers());
myServer.getRestfulServer().getInterceptorService().registerInterceptor(myConsentInterceptor);
// when
@ -767,6 +835,21 @@ public class ConsentInterceptorResourceProviderR4IT extends BaseResourceProvider
}
private Bundle createDocumentBundle() {
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.DOCUMENT);
Composition composition = new Composition();
composition.setId("composition-in-bundle");
Patient patient = new Patient();
patient.setId("patient-in-bundle");
bundle.addEntry().setResource(composition);
bundle.addEntry().setResource(patient);
return bundle;
}
private void createPatientAndOrg() {
myPatientIds = new ArrayList<>();
@ -1095,7 +1178,7 @@ public class ConsentInterceptorResourceProviderR4IT extends BaseResourceProvider
}
private static class ConsentSvcRejectWillSeeResource implements IConsentService {
private static class ConsentSvcWillSeeProceedsBundlesRejectsOthers implements IConsentService {
@Override
public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
if("Bundle".equals(theResource.fhirType())){
@ -1105,5 +1188,27 @@ public class ConsentInterceptorResourceProviderR4IT extends BaseResourceProvider
}
}
private static class ConsentSvcWillSeeAuthorizesBundlesRejectsOthers implements IConsentService {
@Override
public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
if("Bundle".equals(theResource.fhirType())){
return new ConsentOutcome(ConsentOperationStatusEnum.AUTHORIZED);
}
return new ConsentOutcome(ConsentOperationStatusEnum.REJECT);
}
}
private static class ConsentSvcWillSeeRejectsBundlesAuthorizesOthers implements IConsentService {
@Override
public ConsentOutcome willSeeResource(RequestDetails theRequestDetails, IBaseResource theResource, IConsentContextServices theContextServices) {
if("Bundle".equals(theResource.fhirType())){
return new ConsentOutcome(ConsentOperationStatusEnum.REJECT);
}
return new ConsentOutcome(ConsentOperationStatusEnum.AUTHORIZED);
}
}
}

View File

@ -31,7 +31,6 @@ import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.interceptor.consent.ConsentInterceptor;
import ca.uhn.fhir.util.BundleUtil;
import com.google.common.collect.Lists;
import jakarta.annotation.Nonnull;
@ -508,8 +507,7 @@ public class AuthorizationInterceptor implements IRuleApplier {
}
// Don't check the value twice
IdentityHashMap<IBaseResource, Boolean> alreadySeenMap =
ConsentInterceptor.getAlreadySeenResourcesMap(theRequestDetails, myRequestSeenResourcesKey);
IdentityHashMap<IBaseResource, Boolean> alreadySeenMap = getAlreadySeenResourcesMap(theRequestDetails);
if (alreadySeenMap.putIfAbsent(theResponseObject, Boolean.TRUE) != null) {
return;
}
@ -678,4 +676,15 @@ public class AuthorizationInterceptor implements IRuleApplier {
return theResource.getIdElement().getResourceType();
}
@SuppressWarnings("unchecked")
private IdentityHashMap<IBaseResource, Boolean> getAlreadySeenResourcesMap(RequestDetails theRequestDetails) {
IdentityHashMap<IBaseResource, Boolean> alreadySeenResources = (IdentityHashMap<IBaseResource, Boolean>)
theRequestDetails.getUserData().get(myRequestSeenResourcesKey);
if (alreadySeenResources == null) {
alreadySeenResources = new IdentityHashMap<>();
theRequestDetails.getUserData().put(myRequestSeenResourcesKey, alreadySeenResources);
}
return alreadySeenResources;
}
}

View File

@ -221,7 +221,8 @@ public class ConsentInterceptor {
return;
}
IdentityHashMap<IBaseResource, Boolean> authorizedResources = getAuthorizedResourcesMap(theRequestDetails);
IdentityHashMap<IBaseResource, ConsentOperationStatusEnum> alreadySeenResources =
getAlreadySeenResourcesMap(theRequestDetails);
for (int resourceIdx = 0; resourceIdx < thePreResourceAccessDetails.size(); resourceIdx++) {
IBaseResource nextResource = thePreResourceAccessDetails.getResource(resourceIdx);
for (int consentSvcIdx = 0; consentSvcIdx < myConsentService.size(); consentSvcIdx++) {
@ -243,10 +244,11 @@ public class ConsentInterceptor {
case PROCEED:
break;
case AUTHORIZED:
authorizedResources.put(nextResource, Boolean.TRUE);
alreadySeenResources.put(nextResource, ConsentOperationStatusEnum.AUTHORIZED);
skipSubsequentServices = true;
break;
case REJECT:
alreadySeenResources.put(nextResource, ConsentOperationStatusEnum.REJECT);
thePreResourceAccessDetails.setDontReturnResourceAtIndex(resourceIdx);
skipSubsequentServices = true;
break;
@ -307,12 +309,14 @@ public class ConsentInterceptor {
return;
}
IdentityHashMap<IBaseResource, Boolean> authorizedResources = getAuthorizedResourcesMap(theRequestDetails);
IdentityHashMap<IBaseResource, ConsentOperationStatusEnum> alreadySeenResources =
getAlreadySeenResourcesMap(theRequestDetails);
for (int i = 0; i < thePreResourceShowDetails.size(); i++) {
IBaseResource resource = thePreResourceShowDetails.getResource(i);
if (resource == null || authorizedResources.putIfAbsent(resource, Boolean.TRUE) != null) {
if (resource == null
|| alreadySeenResources.putIfAbsent(resource, ConsentOperationStatusEnum.PROCEED) != null) {
continue;
}
@ -329,15 +333,17 @@ public class ConsentInterceptor {
}
continue;
case AUTHORIZED:
alreadySeenResources.put(resource, ConsentOperationStatusEnum.AUTHORIZED);
if (newResource != null) {
thePreResourceShowDetails.setResource(i, newResource);
}
continue;
case REJECT:
alreadySeenResources.put(resource, ConsentOperationStatusEnum.REJECT);
if (nextOutcome.getOperationOutcome() != null) {
IBaseOperationOutcome newOperationOutcome = nextOutcome.getOperationOutcome();
thePreResourceShowDetails.setResource(i, newOperationOutcome);
authorizedResources.put(newOperationOutcome, true);
alreadySeenResources.put(newOperationOutcome, ConsentOperationStatusEnum.PROCEED);
} else {
resource = null;
thePreResourceShowDetails.setResource(i, null);
@ -349,8 +355,8 @@ public class ConsentInterceptor {
}
@Hook(value = Pointcut.SERVER_OUTGOING_RESPONSE)
public void interceptOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResource) {
if (theResource.getResponseResource() == null) {
public void interceptOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails) {
if (theResponseDetails.getResponseResource() == null) {
return;
}
if (isRequestAuthorized(theRequestDetails)) {
@ -366,35 +372,56 @@ public class ConsentInterceptor {
return;
}
IdentityHashMap<IBaseResource, Boolean> authorizedResources = getAuthorizedResourcesMap(theRequestDetails);
// Take care of outer resource first
IdentityHashMap<IBaseResource, ConsentOperationStatusEnum> alreadySeenResources =
getAlreadySeenResourcesMap(theRequestDetails);
if (alreadySeenResources.containsKey(theResponseDetails.getResponseResource())) {
// we've already seen this resource before
ConsentOperationStatusEnum decisionOnResource =
alreadySeenResources.get(theResponseDetails.getResponseResource());
// See outer resource
if (authorizedResources.putIfAbsent(theResource.getResponseResource(), Boolean.TRUE) == null) {
if (ConsentOperationStatusEnum.AUTHORIZED.equals(decisionOnResource)
|| ConsentOperationStatusEnum.REJECT.equals(decisionOnResource)) {
// the consent service decision on the resource was AUTHORIZED or REJECT.
// In both cases, we can immediately return without checking children
return;
}
} else {
// we haven't seen this resource before
// mark it as seen now, set the initial consent decision value to PROCEED by default,
// we will update if it changes another value below
alreadySeenResources.put(theResponseDetails.getResponseResource(), ConsentOperationStatusEnum.PROCEED);
for (IConsentService next : myConsentService) {
final ConsentOutcome outcome = next.willSeeResource(
theRequestDetails, theResource.getResponseResource(), myContextConsentServices);
theRequestDetails, theResponseDetails.getResponseResource(), myContextConsentServices);
if (outcome.getResource() != null) {
theResource.setResponseResource(outcome.getResource());
theResponseDetails.setResponseResource(outcome.getResource());
}
// Clear the total
if (theResource.getResponseResource() instanceof IBaseBundle) {
if (theResponseDetails.getResponseResource() instanceof IBaseBundle) {
BundleUtil.setTotal(
theRequestDetails.getFhirContext(), (IBaseBundle) theResource.getResponseResource(), null);
theRequestDetails.getFhirContext(),
(IBaseBundle) theResponseDetails.getResponseResource(),
null);
}
switch (outcome.getStatus()) {
case REJECT:
alreadySeenResources.put(
theResponseDetails.getResponseResource(), ConsentOperationStatusEnum.REJECT);
if (outcome.getOperationOutcome() != null) {
theResource.setResponseResource(outcome.getOperationOutcome());
theResponseDetails.setResponseResource(outcome.getOperationOutcome());
} else {
theResource.setResponseResource(null);
theResource.setResponseCode(Constants.STATUS_HTTP_204_NO_CONTENT);
theResponseDetails.setResponseResource(null);
theResponseDetails.setResponseCode(Constants.STATUS_HTTP_204_NO_CONTENT);
}
// Return immediately
return;
case AUTHORIZED:
alreadySeenResources.put(
theResponseDetails.getResponseResource(), ConsentOperationStatusEnum.AUTHORIZED);
// Don't check children, so return immediately
return;
case PROCEED:
@ -405,7 +432,7 @@ public class ConsentInterceptor {
}
// See child resources
IBaseResource outerResource = theResource.getResponseResource();
IBaseResource outerResource = theResponseDetails.getResponseResource();
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
IModelVisitor2 visitor = new IModelVisitor2() {
@Override
@ -425,7 +452,7 @@ public class ConsentInterceptor {
}
if (theElement instanceof IBaseResource) {
IBaseResource resource = (IBaseResource) theElement;
if (authorizedResources.putIfAbsent(resource, Boolean.TRUE) != null) {
if (alreadySeenResources.putIfAbsent(resource, ConsentOperationStatusEnum.PROCEED) != null) {
return true;
}
@ -441,12 +468,17 @@ public class ConsentInterceptor {
case REJECT:
replacementResource = childOutcome.getOperationOutcome();
shouldReplaceResource = true;
alreadySeenResources.put(resource, ConsentOperationStatusEnum.REJECT);
break;
case PROCEED:
replacementResource = childOutcome.getResource();
shouldReplaceResource = replacementResource != null;
break;
case AUTHORIZED:
replacementResource = childOutcome.getResource();
shouldReplaceResource = replacementResource != null;
shouldCheckChildren &= childOutcome.getStatus() == ConsentOperationStatusEnum.PROCEED;
shouldCheckChildren = false;
alreadySeenResources.put(resource, ConsentOperationStatusEnum.AUTHORIZED);
break;
}
@ -477,10 +509,6 @@ public class ConsentInterceptor {
ctx.newTerser().visit(outerResource, visitor);
}
private IdentityHashMap<IBaseResource, Boolean> getAuthorizedResourcesMap(RequestDetails theRequestDetails) {
return getAlreadySeenResourcesMap(theRequestDetails, myRequestSeenResourcesKey);
}
@Hook(value = Pointcut.SERVER_HANDLE_EXCEPTION)
public void requestFailed(RequestDetails theRequest, BaseServerResponseException theException) {
theRequest.getUserData().put(myRequestCompletedKey, Boolean.TRUE);
@ -570,14 +598,23 @@ public class ConsentInterceptor {
}
}
/**
* The map returned by this method keeps track of the resources already processed by ConsentInterceptor in the
* context of a request.
* If the map contains a particular resource, it means that the resource has already been processed and the value
* is the status returned by consent services for that resource.
* @param theRequestDetails
* @return
*/
@SuppressWarnings("unchecked")
public static IdentityHashMap<IBaseResource, Boolean> getAlreadySeenResourcesMap(
RequestDetails theRequestDetails, String theKey) {
IdentityHashMap<IBaseResource, Boolean> alreadySeenResources = (IdentityHashMap<IBaseResource, Boolean>)
theRequestDetails.getUserData().get(theKey);
private IdentityHashMap<IBaseResource, ConsentOperationStatusEnum> getAlreadySeenResourcesMap(
RequestDetails theRequestDetails) {
IdentityHashMap<IBaseResource, ConsentOperationStatusEnum> alreadySeenResources =
(IdentityHashMap<IBaseResource, ConsentOperationStatusEnum>)
theRequestDetails.getUserData().get(myRequestSeenResourcesKey);
if (alreadySeenResources == null) {
alreadySeenResources = new IdentityHashMap<>();
theRequestDetails.getUserData().put(theKey, alreadySeenResources);
theRequestDetails.getUserData().put(myRequestSeenResourcesKey, alreadySeenResources);
}
return alreadySeenResources;
}

View File

@ -580,7 +580,10 @@ public class HashMapResourceProvider<T extends IBaseResource> implements IResour
List<IBaseResource> output =
fireInterceptorsAndFilterAsNeeded(Lists.newArrayList(theResource), theRequestDetails);
if (output.size() == 1) {
return theResource;
// do not return theResource here but return whatever the interceptor returned in the list because
// the interceptor might have set the resource in the list to null (if it didn't want it to be returned).
// ConsentInterceptor might do this for example.
return (T) output.get(0);
} else {
return null;
}

View File

@ -34,6 +34,7 @@ import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
@ -61,10 +62,13 @@ import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@ -79,11 +83,14 @@ public class ConsentInterceptorTest {
private int myPort;
private static final DummyPatientResourceProvider ourPatientProvider = new DummyPatientResourceProvider(ourCtx);
private static final DummySystemProvider ourSystemProvider = new DummySystemProvider();
private static final HashMapResourceProvider<Bundle> ourBundleProvider =
new HashMapResourceProvider<>(ourCtx, Bundle.class);
@RegisterExtension
static final RestfulServerExtension ourServer = new RestfulServerExtension(ourCtx)
.registerProvider(ourPatientProvider)
.registerProvider(ourSystemProvider)
.registerProvider(ourBundleProvider)
.withPagingProvider(new FifoMemoryPagingProvider(10));
@Mock(answer = Answers.CALLS_REAL_METHODS)
@ -109,6 +116,7 @@ public class ConsentInterceptorTest {
ourServer.registerInterceptor(myInterceptor);
ourPatientProvider.clear();
ourBundleProvider.clear();
}
@Test
@ -494,6 +502,115 @@ public class ConsentInterceptorTest {
verifyNoMoreInteractions(myConsentSvc);
}
private Bundle createDocumentBundle() {
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.DOCUMENT);
bundle.setId("test-bundle-id");
Composition composition = new Composition();
composition.setId("composition-in-bundle");
Patient patient = new Patient();
patient.setId("patient-in-bundle");
bundle.addEntry().setResource(composition);
bundle.addEntry().setResource(patient);
return bundle;
}
@Test
void testGetBundle_WhenCanSeeReturnsRejectForBundle_WillSeeIsNotCalled() throws IOException {
ourBundleProvider.store(createDocumentBundle());
when(myConsentSvc.canSeeResource(any(),isA(Bundle.class),any())).thenReturn(ConsentOutcome.REJECT);
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Bundle/test-bundle-id");
try (CloseableHttpResponse status = myClient.execute(httpGet)) {
assertEquals(404, status.getStatusLine().getStatusCode());
// response should be an error outcome instead of the resource
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
OperationOutcome outcome = ourCtx.newJsonParser().parseResource(OperationOutcome.class, responseContent);
assertTrue(outcome.hasIssue());
assertEquals(OperationOutcome.IssueSeverity.ERROR, outcome.getIssueFirstRep().getSeverity());
}
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
// willSee should not be called, even for the bundle
verify(myConsentSvc, times(0)).willSeeResource(any(), any(), any());
}
@Test
void testGetBundle_WhenCanSeeReturnsAuthorizedForBundle_WillSeeIsNotCalled() throws IOException {
ourBundleProvider.store(createDocumentBundle());
when(myConsentSvc.canSeeResource(any(),isA(Bundle.class),any())).thenReturn(ConsentOutcome.AUTHORIZED);
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Bundle/test-bundle-id");
try (CloseableHttpResponse status = myClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
}
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
// willSee should not be called, even for the bundle
verify(myConsentSvc, times(0)).willSeeResource(any(), any(), any());
}
@Test
void testGetBundle_WhenWillSeeReturnsRejectForBundle_WillSeeIsNotCalledForChildResources() throws IOException {
ourBundleProvider.store(createDocumentBundle());
when(myConsentSvc.canSeeResource(any(),any(),any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.willSeeResource(any(),isA(Bundle.class),any())).thenReturn(ConsentOutcome.REJECT);
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Bundle/test-bundle-id");
try (CloseableHttpResponse status = myClient.execute(httpGet)) {
assertEquals(404, status.getStatusLine().getStatusCode());
// response should be an error outcome instead of the resource
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
OperationOutcome outcome = ourCtx.newJsonParser().parseResource(OperationOutcome.class, responseContent);
assertTrue(outcome.hasIssue());
assertEquals(OperationOutcome.IssueSeverity.ERROR, outcome.getIssueFirstRep().getSeverity());
}
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
// will see should be called only once, for the bundle
verify(myConsentSvc, times(1)).willSeeResource(any(), any(), any());
}
@Test
void testGetBundle_WhenWillSeeReturnsAuthorizedForBundle_WillSeeIsNotCalledForChildResources() throws IOException {
ourBundleProvider.store(createDocumentBundle());
when(myConsentSvc.canSeeResource(any(),any(),any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.willSeeResource(any(),isA(Bundle.class),any())).thenReturn(ConsentOutcome.AUTHORIZED);
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Bundle/test-bundle-id");
try (CloseableHttpResponse status = myClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
}
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
// willSee should only be called once, for the bundle
verify(myConsentSvc, times(1)).willSeeResource(any(), any(), any());
}
@Test
void testGetBundle_WhenWillSeeReturnsProceedForBundle_WillSeeIsCalledForChildResources() throws IOException {
ourBundleProvider.store(createDocumentBundle());
when(myConsentSvc.canSeeResource(any(),any(),any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.willSeeResource(any(),isA(Bundle.class),any())).thenReturn(ConsentOutcome.PROCEED);
// the test bundle contains a Composition and a Patient, we expect calls to them in this case
when(myConsentSvc.willSeeResource(any(),isA(Composition.class),any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.willSeeResource(any(),isA(Patient.class),any())).thenReturn(ConsentOutcome.PROCEED);
HttpGet httpGet = new HttpGet("http://localhost:" + myPort + "/Bundle/test-bundle-id");
try (CloseableHttpResponse status = myClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
}
verify(myConsentSvc, times(1)).canSeeResource(any(), any(), any());
// expect willSee to be called 3 times: 1 for the bundle, 1 for composition child and 1 for Patient child
verify(myConsentSvc, times(1)).willSeeResource(any(), isA(Bundle.class), any());
verify(myConsentSvc, times(1)).willSeeResource(any(), isA(Composition.class), any());
verify(myConsentSvc, times(1)).willSeeResource(any(), isA(Patient.class), any());
}
@Test
public void testPage_SeeResourceReplacesInnerResource() throws IOException {
Patient pta = (Patient) new Patient().setActive(true).setId("PTA");