Skip to content

Commit

Permalink
issue #3390 - add implicit search/history _type scoping
Browse files Browse the repository at this point in the history
When the fhirServer/resources config restricts search or history on one
or more resource types, we now add an implicit _type parameter to the
request to filter it down to just the resource types that support the
given interaction.

Signed-off-by: Lee Surprenant <[email protected]>
  • Loading branch information
lmsurpre committed Mar 24, 2022
1 parent 1b48918 commit a96c309
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ public ResourcesConfigAdapter(PropertyGroup resourcesConfig) throws Exception {
}
}

/**
* @return whether the server is configured to prevent searches for one or more resource types
*/
public boolean isSearchRestricted() {
Set<String> searchableResourceTypes = typesByInteraction.get(Interaction.SEARCH);
return searchableResourceTypes == null || searchableResourceTypes.size() < ALL_CONCRETE_TYPES.size();
}

/**
* @return whether the server is configured to prevent history interactions for one or more resource types
*/
public boolean isHistoryRestricted() {
Set<String> resourceTypesSupportingHistory = typesByInteraction.get(Interaction.HISTORY);
return resourceTypesSupportingHistory == null || resourceTypesSupportingHistory.size() < ALL_CONCRETE_TYPES.size();
}

/**
* @return an immutable, non-null set of concrete supported resource types
* @throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.util.Set;
import java.util.logging.Logger;

import org.owasp.encoder.Encode;

import com.ibm.fhir.config.FHIRConfigHelper;
import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
Expand All @@ -39,8 +41,6 @@
import com.ibm.fhir.persistence.context.impl.FHIRSystemHistoryContextImpl;
import com.ibm.fhir.persistence.exception.FHIRPersistenceException;

import org.owasp.encoder.Encode;

public class FHIRPersistenceUtil {
private static final Logger log = Logger.getLogger(FHIRPersistenceUtil.class.getName());

Expand Down Expand Up @@ -104,6 +104,10 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map<String,
context.setLenient(lenient);
context.setHistorySortOrder(HistorySortOrder.DESC_LAST_UPDATED); // default is most recent first
try {
PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> typesSupportingHistory = config.getSupportedResourceTypes(Interaction.HISTORY);

for (String name : queryParameters.keySet()) {
List<String> values = queryParameters.get(name);
String first = values.get(0);
Expand All @@ -120,10 +124,6 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map<String,
continue;
}

PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> typesSupportingHistory = config.getSupportedResourceTypes(Interaction.HISTORY);

for (String v: values) {
List<String> resourceTypes = Arrays.asList(v.split("\\s*,\\s*"));
for (String resourceType: resourceTypes) {
Expand Down Expand Up @@ -187,6 +187,12 @@ public static FHIRSystemHistoryContext parseSystemHistoryParameters(Map<String,
}
}

// if no _type parameter was passed but the history interaction is only supported for some subset of types
// then we need to set the supported resource types in the context
if (context.getResourceTypes().isEmpty() && config.isHistoryRestricted()) {
typesSupportingHistory.stream().forEach(context::addResourceType);
}

// Grab the return preference from the request context. We add it to the history
// context so we have everything we need in one place
FHIRRequestContext requestContext = FHIRRequestContext.get();
Expand Down Expand Up @@ -240,7 +246,7 @@ public static ResourceResult<Resource> createDeletedResourceResultMarker(String
public static com.ibm.fhir.model.type.Instant getUpdateTime() {
return com.ibm.fhir.model.type.Instant.now(ZoneOffset.UTC);
}

/**
* Creates and returns a copy of the passed resource with the {@code Resource.id}
* {@code Resource.meta.versionId}, and {@code Resource.meta.lastUpdated} elements replaced.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.owasp.encoder.Encode;

import com.ibm.fhir.config.FHIRConfigHelper;
import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
import com.ibm.fhir.config.Interaction;
import com.ibm.fhir.config.PropertyGroup;
import com.ibm.fhir.config.ResourcesConfigAdapter;
import com.ibm.fhir.config.PropertyGroup.PropertyEntry;
import com.ibm.fhir.config.ResourcesConfigAdapter;
import com.ibm.fhir.core.FHIRConstants;
import com.ibm.fhir.model.resource.CodeSystem;
import com.ibm.fhir.model.resource.CodeSystem.Concept;
Expand Down Expand Up @@ -83,8 +85,6 @@
import com.ibm.fhir.term.util.CodeSystemSupport;
import com.ibm.fhir.term.util.ValueSetSupport;

import org.owasp.encoder.Encode;

/**
* A helper class with methods for working with HL7 FHIR search.
*/
Expand Down Expand Up @@ -404,17 +404,21 @@ public FHIRSearchContext parseQueryParameters(Class<?> resourceType,
throw SearchExceptionUtil.buildNewInvalidSearchException("system search not supported with _include or _revinclude.");
}

// _type parameter is only supported for whole system searches
boolean isSystemSearch = Resource.class.equals(resourceType);
if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE) && isSystemSearch) {
manageException("_type parameter is only supported for whole-system search", IssueType.NOT_SUPPORTED, context, false);
}

// Process the _type parameter(s)
if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE)) {
if (!Resource.class.equals(resourceType)) {
manageException("_type search parameter is only supported with system search", IssueType.NOT_SUPPORTED, context, false);
} else {
if (isSystemSearch) {
PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> searchableTypes = config.getSupportedResourceTypes(Interaction.SEARCH);

if (queryParameters.containsKey(SearchConstants.RESOURCE_TYPE)) {
List<String> values = queryParameters.get(SearchConstants.RESOURCE_TYPE);

PropertyGroup pg = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
ResourcesConfigAdapter config = new ResourcesConfigAdapter(pg);
Set<String> searchableTypes = config.getSupportedResourceTypes(Interaction.SEARCH);

for (String v: values) {
List<String> tmpResourceTypes = Arrays.asList(v.split("\\s*,\\s*"));
for (String tmpResourceType: tmpResourceTypes) {
Expand All @@ -430,6 +434,12 @@ public FHIRSearchContext parseQueryParameters(Class<?> resourceType,
}
}
}

// if no _type parameter was passed but the search interaction is only supported for some subset of types
// then we need to set the supported resource types in the context
if (resourceTypes.isEmpty() && config.isSearchRestricted()) {
resourceTypes.addAll(config.getSupportedResourceTypes(Interaction.SEARCH));
}
}

queryParameters.remove(SearchConstants.RESOURCE_TYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,32 @@ public void testSearchAll_UrlReflexsivityUsingLastUpdated() throws Exception {
}
}

@Test(groups = { "server-search-all" }, dependsOnMethods = { "testCreatePatient" })
public void testSearchAllUsingId_tenant1() throws Exception {
// tenant1 is configured to support search on only a subset of resource types
FHIRRequestHeader altTenantHeader = new FHIRRequestHeader("X-FHIR-TENANT-ID", "tenant1");
FHIRRequestHeader altDatastoreHeader = new FHIRRequestHeader("X-FHIR-DSID", "profile");

FHIRParameters parameters = new FHIRParameters();
parameters.searchParam("_id", patientId);
FHIRResponse response = client.searchAll(parameters, false, altTenantHeader, altDatastoreHeader);
assertResponse(response.getResponse(), Response.Status.OK.getStatusCode());
Bundle bundle = response.getResource(Bundle.class);
assertNotNull(bundle);

boolean validSelf = false;
for (Link link : bundle.getLink()) {
String type = link.getRelation().getValue();
String uri = link.getUrl().getValue();
if ("self".equals(type)) {
uri.contains("_type");
validSelf = true;
}
}

assertTrue(validSelf, "missing self link");
}

/*
* Queries based on the URI the endpoint with the query parameter and value.
*/
Expand Down
Loading

0 comments on commit a96c309

Please sign in to comment.