Skip to content

Commit

Permalink
issue #1351 - Check _include and _revinclude values are supported
Browse files Browse the repository at this point in the history
Signed-off-by: Troy Biesterfeld <tbieste@us.ibm.com>
  • Loading branch information
tbieste committed Oct 20, 2020
1 parent 7097687 commit e653558
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ public class FHIRConfiguration {
public static final String PROPERTY_FIELD_RESOURCES_OPEN = "open";
public static final String PROPERTY_FIELD_RESOURCES_INTERACTIONS = "interactions";
public static final String PROPERTY_FIELD_RESOURCES_SEARCH_PARAMETERS = "searchParameters";
public static final String PROPERTY_FIELD_RESOURCES_SEARCH_INCLUDES = "searchIncludes";
public static final String PROPERTY_FIELD_RESOURCES_SEARCH_REV_INCLUDES = "searchRevIncludes";

// Auth and security properties
public static final String PROPERTY_SECURITY_CORS = "fhirServer/security/cors";
Expand Down
133 changes: 122 additions & 11 deletions fhir-search/src/main/java/com/ibm/fhir/search/util/SearchUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -1485,8 +1485,8 @@ public static QueryParameter parseChainedInclusionCriteria(QueryParameter inclus
if (parmNames[i].indexOf(SearchConstants.COLON_DELIMITER) != -1) {
qualifiedInclusionCriteria = parmNames[i].split(SearchConstants.COLON_DELIMITER_STR);
chainedInclusionCriteria =
new QueryParameter(Type.REFERENCE, qualifiedInclusionCriteria[0], null, resourceType,
inclusionCriteriaParm.getValues());
new QueryParameter(Type.REFERENCE, qualifiedInclusionCriteria[0], null,
resourceType, inclusionCriteriaParm.getValues());
} else {
chainedInclusionCriteria =
new QueryParameter(Type.REFERENCE, parmNames[i], null, resourceType);
Expand Down Expand Up @@ -1549,7 +1549,10 @@ private static void parseInclusionParameter(Class<?> resourceType, FHIRSearchCon

SearchParameter searchParm;
InclusionParameter newInclusionParm;
List<InclusionParameter> newInclusionParms;
List<InclusionParameter> newInclusionParms = null;

List<String> allowedIncludes = getSearchIncludeRestrictions(resourceType.getSimpleName());
List<String> allowedRevIncludes = getSearchRevIncludeRestrictions(resourceType.getSimpleName());

for (String inclusionValue : inclusionValues) {

Expand All @@ -1562,16 +1565,33 @@ private static void parseInclusionParameter(Class<?> resourceType, FHIRSearchCon
searchParameterName = inclusionValueParts[1];
searchParameterTargetType = inclusionValueParts.length == 3 ? inclusionValueParts[2] : null;

// For _include parameter, join resource type must match resource type being searched
if (SearchConstants.INCLUDE.equals(inclusionKeyword) && !joinResourceType.equals(resourceType.getSimpleName())) {
throw SearchExceptionUtil.buildNewInvalidSearchException(
"The join resource type must match the resource type being searched.");
if (SearchConstants.INCLUDE.equals(inclusionKeyword)) {

// For _include parameter, join resource type must match resource type being searched
if (!joinResourceType.equals(resourceType.getSimpleName())) {
throw SearchExceptionUtil.buildNewInvalidSearchException(
"The join resource type must match the resource type being searched.");
}

// Check allowed _include values
if (allowedIncludes != null && !allowedIncludes.contains(inclusionValue)) {
throw SearchExceptionUtil.buildNewInvalidSearchException("'" + inclusionValue
+ "' is not a valid _include parameter value for resource type '" + resourceType.getSimpleName() + "'");
}
}

// For _revinclude parameter, target resource type, if specified, must match resource type being searched
if (SearchConstants.REVINCLUDE.equals(inclusionKeyword) && searchParameterTargetType != null
&& !searchParameterTargetType.equals(resourceType.getSimpleName())) {
throw SearchExceptionUtil.buildNewInvalidSearchException("The search parameter target type must match the resource type being searched.");
if (SearchConstants.REVINCLUDE.equals(inclusionKeyword)) {

// For _revinclude parameter, target resource type, if specified, must match resource type being searched
if (searchParameterTargetType != null && !searchParameterTargetType.equals(resourceType.getSimpleName())) {
throw SearchExceptionUtil.buildNewInvalidSearchException("The search parameter target type must match the resource type being searched.");
}

// Check allowed _revinclude values
if (allowedRevIncludes != null && !allowedRevIncludes.contains(inclusionValue)) {
throw SearchExceptionUtil.buildNewInvalidSearchException("'" + inclusionValue
+ "' is not a valid _revinclude parameter value for resource type '" + resourceType.getSimpleName() + "'");
}
}

// Ensure that the Inclusion Parameter being parsed is a valid search parameter of type 'reference'.
Expand Down Expand Up @@ -1603,6 +1623,7 @@ private static void parseInclusionParameter(Class<?> resourceType, FHIRSearchCon
newInclusionParms =
buildIncludeParameter(resourceType, joinResourceType, entry.getValue(), entry.getKey(), searchParameterTargetType);
context.getIncludeParameters().addAll(newInclusionParms);

} else {
newInclusionParm =
buildRevIncludeParameter(resourceType, joinResourceType, entry.getValue(), entry.getKey(), searchParameterTargetType);
Expand All @@ -1612,6 +1633,96 @@ private static void parseInclusionParameter(Class<?> resourceType, FHIRSearchCon
}
}

/**
* Retrieves the search include restrictions.
*
* @param resourceType
* the resource type
* @return list of allowed search _include values, or null if no restrictions
* @throws Exception
* an exception
*/
private static List<String> getSearchIncludeRestrictions(String resourceType) throws Exception {

// Retrieve the "resources" config property group.
PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
if (rsrcsGroup != null) {
List<PropertyEntry> rsrcsEntries = rsrcsGroup.getProperties();
if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) {

// Try find search includes property for matching resource type
for (PropertyEntry rsrcsEntry : rsrcsEntries) {
if (resourceType.equals(rsrcsEntry.getName())) {
PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue();
if (resourceTypeGroup != null) {
return resourceTypeGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_INCLUDES);
}
}
}

// Otherwise, try find search includes property for "Resource" resource type
for (PropertyEntry rsrcsEntry : rsrcsEntries) {

// Check if matching resource type
if (SearchConstants.RESOURCE_RESOURCE.equals(rsrcsEntry.getName())) {
PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue();
if (resourceTypeGroup != null) {
return resourceTypeGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_INCLUDES);
}
}
}

}
}

return null;
}

/**
* Retrieves the search revinclude restrictions.
*
* @param resourceType
* the resource type
* @return list of allowed search _revinclude values, or null if no restrictions
* @throws Exception
* an exception
*/
private static List<String> getSearchRevIncludeRestrictions(String resourceType) throws Exception {

// Retrieve the "resources" config property group.
PropertyGroup rsrcsGroup = FHIRConfigHelper.getPropertyGroup(FHIRConfiguration.PROPERTY_RESOURCES);
if (rsrcsGroup != null) {
List<PropertyEntry> rsrcsEntries = rsrcsGroup.getProperties();
if (rsrcsEntries != null && !rsrcsEntries.isEmpty()) {

// Try find search revincludes property for matching resource type
for (PropertyEntry rsrcsEntry : rsrcsEntries) {
if (resourceType.equals(rsrcsEntry.getName())) {
PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue();
if (resourceTypeGroup != null) {
return resourceTypeGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_REV_INCLUDES);
}
}
}

// Otherwise, try find search revincludes property for "Resource" resource type
for (PropertyEntry rsrcsEntry : rsrcsEntries) {

// Check if matching resource type
if (SearchConstants.RESOURCE_RESOURCE.equals(rsrcsEntry.getName())) {
PropertyGroup resourceTypeGroup = (PropertyGroup) rsrcsEntry.getValue();
if (resourceTypeGroup != null) {
return resourceTypeGroup.getStringListProperty(FHIRConfiguration.PROPERTY_FIELD_RESOURCES_SEARCH_REV_INCLUDES);
}
}
}

}
}

return null;
}

/**
* Builds and returns a collection of InclusionParameter objects representing
* occurrences of the _include search result parameter in the query string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@

import com.ibm.fhir.config.FHIRConfiguration;
import com.ibm.fhir.config.FHIRRequestContext;
import com.ibm.fhir.model.resource.ExplanationOfBenefit;
import com.ibm.fhir.model.resource.MedicationRequest;
import com.ibm.fhir.model.resource.Organization;
import com.ibm.fhir.model.resource.Patient;
import com.ibm.fhir.model.resource.Person;
import com.ibm.fhir.model.resource.RelatedPerson;
import com.ibm.fhir.search.exception.FHIRSearchException;
import com.ibm.fhir.search.test.BaseSearchTest;
import com.ibm.fhir.search.util.SearchUtil;
Expand All @@ -27,6 +32,7 @@
*/
public class SearchParameterRestrictionTest extends BaseSearchTest {

private static final String DEFAULT_TENANT_ID = "default";
private static final String TENANT_ID = "tenant7";

@Override
Expand Down Expand Up @@ -124,5 +130,124 @@ public void testModifierDisallowed() throws Exception {

SearchUtil.parseQueryParameters(Patient.class, queryParameters);
}

@Test
public void testIncludeAllowedByDefault() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(DEFAULT_TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("Person:organization"));

SearchUtil.parseQueryParameters(Person.class, queryParameters);
}

@Test
public void testIncludeAllowed() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("Patient:general-practitioner"));

SearchUtil.parseQueryParameters(Patient.class, queryParameters);
}

@Test
public void testIncludeWildcardAllowed() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("ExplanationOfBenefit:*"));

SearchUtil.parseQueryParameters(ExplanationOfBenefit.class, queryParameters);
}

@Test(expectedExceptions = { FHIRSearchException.class })
public void testIncludeWildcardNotAllowed() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("Patient:*"));

SearchUtil.parseQueryParameters(Patient.class, queryParameters);
}

@Test
public void testIncludeAllowedByBaseResource() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("MedicationRequest:patient"));

SearchUtil.parseQueryParameters(MedicationRequest.class, queryParameters);
}

@Test(expectedExceptions = { FHIRSearchException.class })
public void testIncludeDisallowed() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("Patient:organization"));

SearchUtil.parseQueryParameters(Patient.class, queryParameters);
}

@Test(expectedExceptions = { FHIRSearchException.class })
public void testIncludeDisallowedByBaseResource() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_include", Collections.singletonList("Person:organization"));

SearchUtil.parseQueryParameters(Person.class, queryParameters);
}

@Test
public void testRevIncludeAllowedByDefault() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(DEFAULT_TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_revinclude", Collections.singletonList("Person:organization"));

SearchUtil.parseQueryParameters(Organization.class, queryParameters);
}

@Test
public void testRevIncludeAllowed() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_revinclude", Collections.singletonList("MedicationRequest:intended-performer"));

SearchUtil.parseQueryParameters(Patient.class, queryParameters);
}

@Test
public void testRevIncludeAllowedByBaseResource() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_revinclude", Collections.singletonList("Provenance:target"));

SearchUtil.parseQueryParameters(Person.class, queryParameters);
}

@Test(expectedExceptions = { FHIRSearchException.class })
public void testRevIncludeDisallowed() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_revinclude", Collections.singletonList("MedicationRequest:requester"));

SearchUtil.parseQueryParameters(Patient.class, queryParameters);
}

@Test(expectedExceptions = { FHIRSearchException.class })
public void testRevIncludeDisallowedByBaseResource() throws Exception {
FHIRRequestContext.set(new FHIRRequestContext(TENANT_ID));

Map<String, List<String>> queryParameters = new HashMap<>();
queryParameters.put("_revinclude", Collections.singletonList("MedicationRequest:intended-performer"));

SearchUtil.parseQueryParameters(RelatedPerson.class, queryParameters);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"ExplanationOfBenefit:provider",
"ExplanationOfBenefit:care-team",
"ExplanationOfBenefit:coverage",
"ExplanationOfBenefit:insurer"
"ExplanationOfBenefit:insurer",
"ExplanationOfBenefit:*"
],
"searchRevIncludes": [],
"searchParameters": {
Expand All @@ -26,6 +27,14 @@
"identifier": "http://hl7.org/fhir/us/carin-bb/SearchParameter/explanationofbenefit-identifier",
"service-date": "http://hl7.org/fhir/us/carin-bb/SearchParameter/explanationofbenefit-service-date"
}
},
"Patient": {
"searchIncludes": ["Patient:general-practitioner"],
"searchRevIncludes": ["MedicationRequest:intended-performer"]
},
"Resource": {
"searchIncludes": ["MedicationRequest:patient"],
"searchRevIncludes": ["Provenance:target"]
}
}
}
Expand Down

0 comments on commit e653558

Please sign in to comment.