The SearchNarrowingInterceptor can be used to automatically narrow or constrain the scope of FHIR searches.
This interceptor is designed to be used in conjunction with the Authorization Interceptor. It uses a similar strategy where a dynamic list is built up for each request, but the purpose of this interceptor is to modify client searches that are received (after HAPI FHIR receives the HTTP request, but before the search is actually performed) to restrict the search to only search for specific resources or compartments that the user has access to.
This could be used, for example, to allow the user to perform a search for:
http://baseurl/Observation
...and then receive results as though they had requested:
http://baseurl/Observation
An example of this interceptor follows:
public class MyPatientSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
/**
* This method must be overridden to provide the list of compartments
* and/or resources that the current user should have access to
*/
@Override
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
// Process authorization header - The following is a fake
// implementation. Obviously we'd want something more real
// for a production scenario.
//
// In this basic example we have two hardcoded bearer tokens,
// one which is for a user that has access to one patient, and
// another that has full access.
String authHeader = theRequestDetails.getHeader("Authorization");
if ("Bearer dfw98h38r".equals(authHeader)) {
// This user will have access to two compartments
return new AuthorizedList().addCompartment("Patient/123").addCompartment("Patient/456");
} else if ("Bearer 39ff939jgg".equals(authHeader)) {
// This user has access to everything
return new AuthorizedList();
} else {
throw new AuthenticationException("Unknown bearer token");
}
}
}
By default, this interceptor will narrow URLs for FHIR search operations only. The interceptor can also be configured to narrow URLs on conditional operations.
When this feature is enabled request URLs are also narrowed for the following FHIR operations:
If-None-Exist
header is narrowed)The following example shows how to enable conditional URL narrowing on the interceptor.
SearchNarrowingInterceptor interceptor = new SearchNarrowingInterceptor();
interceptor.setNarrowConditionalUrls(true);
SearchNarrowingInterceptor can also be used to narrow searches by automatically appending token:in
and token:not-in
parameters.
In the example below, searches are narrowed as shown below:
Important note: ValueSet Membership rules are only applied in cases where the ValueSet expansion has a code count below a configurable threshold (default is 500). To narrow searches with a larger ValueSet expansion, it is necessary to also enable ResultSet Narrowing.
public class MyCodeSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
/**
* This method must be overridden to provide the list of compartments
* and/or resources that the current user should have access to
*/
@Override
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
// Process authorization header - The following is a fake
// implementation. Obviously we'd want something more real
// for a production scenario.
String authHeader = theRequestDetails.getHeader("Authorization");
if ("Bearer dfw98h38r".equals(authHeader)) {
return new AuthorizedList()
// When searching for Observations, narrow the search to only include Observations
// with a code indicating that it is a Vital Signs Observation
.addCodeInValueSet(
"Observation", "code", "http://hl7.org/fhir/ValueSet/observation-vitalsignresult")
// When searching for Encounters, narrow the search to exclude Encounters where
// the Encounter class is in a ValueSet containing forbidden class codes
.addCodeNotInValueSet("Encounter", "class", "http://my-forbidden-encounter-classes");
} else {
throw new AuthenticationException("Unknown bearer token");
}
}
}
By default, narrowing will simply modify search parameters in order to automatically constrain the results that are returned to the client. This is helpful for situations where the resource type you are trying to filter is the primary type of the search, but is less helpful when it is not.
For example suppose you wanted to narrow searches for Observations to only include Observations with a code in http://my-value-set
. When a search is performed for Observation?subject=Patient/123
the SearchNarrowingInterceptor will typically modify this to be performed as Observation?subject=Patient/123&code:in=http://my-value-set
.
However this is not always possible:
:in
clause and the SearchNarrowingInterceptor will not do so._include
or _revinclude
parameter, it is not possible to filter it by adding URL parameters.Patient/[id]/$expand
), it is not possible to filter it by adding URL parameters.To enable ResultSet narrowing, the SearchNarrowingInterceptor is used along with the ConsentInterceptor, and the ConsentInterceptor is configured to include a companion consent service implementation that works with search narrowing rules. This is shown in the following example:
SearchNarrowingInterceptor narrowingInterceptor = new SearchNarrowingInterceptor() {
@Override
protected AuthorizedList buildAuthorizedList(RequestDetails theRequestDetails) {
// Your rules go here
return new AuthorizedList()
.addCodeInValueSet(
"Observation", "code", "http://hl7.org/fhir/ValueSet/observation-vitalsignresult");
}
};
restfulServer.registerInterceptor(narrowingInterceptor);
// Create a consent service for search narrowing
IValidationSupport validationSupport = null; // This needs to be populated
FhirContext searchParamRegistry = null; // This needs to be populated
SearchNarrowingConsentService consentService =
new SearchNarrowingConsentService(validationSupport, searchParamRegistry);
// Create a ConsentInterceptor to apply the ConsentService and register it with the server
ConsentInterceptor consentInterceptor = new ConsentInterceptor();
consentInterceptor.registerConsentService(consentService);
restfulServer.registerInterceptor(consentInterceptor);