3418 get resource, response doesn't contain total if consent service enabled (#3420)

* throw an exception if parameter in use

* fix string

* add msg code

* fix msg code

* only check _total=accurate

* extract validateParameter()

* update validateParameter
This commit is contained in:
katiesmilecdr 2022-02-24 17:11:14 -05:00 committed by GitHub
parent dbc9427a05
commit c9b87d7c67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 3 deletions

View File

@ -0,0 +1,4 @@
---
type: fix
issue: 3418
title: "`GET` resource with `_total=accurate` and `_summary=count` if consent service enabled should throw an InvalidRequestException. This issue has been fixed."

View File

@ -17,7 +17,7 @@ The consent interceptor has several primary purposes:
* It can apply consent directives (e.g. by reading relevant Consent resources)
* The consent service suppresses search the total being returned in Bundle.total for search results.
* The consent service suppresses search the total being returned in Bundle.total for search results, even if the user explicitly requested them using the `_total=accurate` or `_summary=count` parameter.
The ConsentInterceptor requires a user-supplied instance of the [IConsentService](/hapi-fhir/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/consent/IConsentService.html) interface. The following shows a simple example of an IConsentService implementation:

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.server.interceptor.consent;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
@ -33,12 +34,14 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.ResponseDetails;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.util.ICachedSearchDetails;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.IModelVisitor2;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@ -100,6 +103,9 @@ public class ConsentInterceptor {
if (isAllowListedRequest(theRequestDetails)) {
return;
}
validateParameter(theRequestDetails.getParameters());
ConsentOutcome outcome = myConsentService.startOperation(theRequestDetails, myContextConsentServices);
Validate.notNull(outcome, "Consent service returned null outcome");
@ -357,4 +363,15 @@ public class ConsentInterceptor {
private boolean isMetadataPath(RequestDetails theRequestDetails) {
return URL_TOKEN_METADATA.equals(theRequestDetails.getRequestPath());
}
private void validateParameter(Map<String, String[]> theParameterMap) {
if (theParameterMap != null) {
if (theParameterMap.containsKey("_total") && Arrays.stream(theParameterMap.get("_total")).anyMatch("accurate"::equals)) {
throw new InvalidRequestException(Msg.code(2037) + "_total=accurate is not permitted on this server");
}
if (theParameterMap.containsKey("_summary") && Arrays.stream(theParameterMap.get("_summary")).anyMatch("count"::equals)) {
throw new InvalidRequestException(Msg.code(2038) + "_summary=count is not permitted on this server");
}
}
}
}

View File

@ -148,11 +148,64 @@ public class ConsentInterceptorTest {
HttpGet httpGet;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=accurate");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(400, status.getStatusLine().getStatusCode());
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", responseContent);
assertThat(responseContent, containsString(Msg.code(2037) + "_total=accurate is not permitted on this server"));
}
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.willSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=estimated");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", responseContent);
assertThat(responseContent, not(containsString("\"total\"")));
}
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=accurate");
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=none");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", responseContent);
assertThat(responseContent, not(containsString("\"total\"")));
}
}
@Test
public void testSummaryModeIgnoredTotalForConsentQueries() throws IOException {
Patient patientA = new Patient();
patientA.setId("Patient/A");
patientA.setActive(true);
patientA.addName().setFamily("FAMILY").addGiven("GIVEN");
patientA.addIdentifier().setSystem("SYSTEM").setValue("VALUEA");
ourPatientProvider.store(patientA);
Patient patientB = new Patient();
patientB.setId("Patient/B");
patientB.setActive(true);
patientB.addName().setFamily("FAMILY").addGiven("GIVEN");
patientB.addIdentifier().setSystem("SYSTEM").setValue("VALUEB");
ourPatientProvider.store(patientB);
HttpGet httpGet;
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=count");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(400, status.getStatusLine().getStatusCode());
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response: {}", responseContent);
assertThat(responseContent, containsString(Msg.code(2038) + "_summary=count is not permitted on this server"));
}
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.canSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
when(myConsentSvc.willSeeResource(any(), any(), any())).thenReturn(ConsentOutcome.PROCEED);
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=data");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
@ -161,7 +214,7 @@ public class ConsentInterceptorTest {
}
when(myConsentSvc.startOperation(any(), any())).thenReturn(ConsentOutcome.AUTHORIZED);
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_total=accurate");
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_summary=data");
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
assertEquals(200, status.getStatusLine().getStatusCode());
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);