Fix #227 - JPA server should reject resources with a reference that

points to an incorrectly typed
				resource (e.g. points to Patient/123 but
resource 123 is actually an Observation) or points
				to a resource that is not valid in the
location it is found in (e.g. points to Patient/123 but
				the field supposed to reference an
Organization)
This commit is contained in:
James Agnew 2015-09-24 14:47:04 -04:00
parent 072c1ece87
commit 4ff7452c9b
3 changed files with 110 additions and 1 deletions

View File

@ -65,9 +65,11 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.entity.BaseHasResource;
@ -171,6 +173,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
RuntimeResourceDefinition def = getContext().getResourceDefinition(theResource);
for (RuntimeSearchParam nextSpDef : def.getSearchParams()) {
if (nextSpDef.getParamType() != RestSearchParameterTypeEnum.REFERENCE) {
continue;
}
@ -187,6 +191,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
String[] nextPathsSplit = nextPathsUnsplit.split("\\|");
for (String nextPath : nextPathsSplit) {
List<Class<? extends IBaseResource>> allowedTypesInField = null;
for (Object nextObject : extractValues(nextPath, theResource)) {
if (nextObject == null) {
continue;
@ -240,10 +245,43 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
ResourceTable target = myEntityManager.find(ResourceTable.class, valueOf);
RuntimeResourceDefinition targetResourceDef = getContext().getResourceDefinition(type);
if (target == null) {
String resName = getContext().getResourceDefinition(type).getName();
String resName = targetResourceDef.getName();
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
}
if (!typeString.equals(target.getResourceType())) {
throw new UnprocessableEntityException("Resource contains reference to " + nextValue.getReference().getValue() + " but resource with ID " + nextValue.getReference().getIdPart() + " is actually of type " + target.getResourceType());
}
/*
* Is the target type an allowable type of resource for the path where it is referenced?
*/
if (allowedTypesInField == null) {
BaseRuntimeChildDefinition childDef = getContext().newTerser().getDefinition(theResource.getClass(), nextPath);
if (childDef instanceof RuntimeChildResourceDefinition) {
RuntimeChildResourceDefinition resRefDef = (RuntimeChildResourceDefinition) childDef;
allowedTypesInField = resRefDef.getResourceTypes();
} else {
allowedTypesInField = new ArrayList<Class<? extends IBaseResource>>();
allowedTypesInField.add(IBaseResource.class);
}
}
boolean acceptableLink = false;
for(Class<? extends IBaseResource> next : allowedTypesInField) {
if (next.isAssignableFrom(targetResourceDef.getImplementingClass())) {
acceptableLink = true;
break;
}
}
if (!acceptableLink) {
throw new UnprocessableEntityException("Invalid reference found at path '" + nextPath + "'. Resource type '" + targetResourceDef.getName() + "' is not valid for this path");
}
nextEntity = new ResourceLink(nextPath, theEntity, target);
} else {
if (!multiType) {

View File

@ -163,6 +163,71 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
}
@Test
public void testCreateWithInvalid() {
Observation o1 = new Observation();
o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01");
o1.setValue(new CodeableConceptDt("testChoiceParam01CCS", "testChoiceParam01CCV"));
IIdType id1 = myObservationDao.create(o1).getId();
{
IBundleProvider found = myObservationDao.search(Observation.SP_VALUE_CONCEPT, new TokenParam("testChoiceParam01CCS", "testChoiceParam01CCV"));
assertEquals(1, found.size());
assertEquals(id1, found.getResources(0, 1).get(0).getIdElement());
}
}
@Test
public void testCreateWithIllegalReference() {
Observation o1 = new Observation();
o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01");
IIdType id1 = myObservationDao.create(o1).getId().toUnqualifiedVersionless();
try {
Patient p = new Patient();
p.getManagingOrganization().setReference(id1);
myPatientDao.create(p);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Invalid reference found at path 'Patient.managingOrganization'. Resource type 'Observation' is not valid for this path", e.getMessage());
}
try {
Patient p = new Patient();
p.getManagingOrganization().setReference(new IdDt("Organization", id1.getIdPart()));
myPatientDao.create(p);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Resource contains reference to Organization/1 but resource with ID 1 is actually of type Observation", e.getMessage());
}
// Now with a forced ID
o1 = new Observation();
o1.setId("testCreateWithIllegalReference");
o1.getCode().addCoding().setSystem("foo").setCode("testChoiceParam01");
id1 = myObservationDao.update(o1).getId().toUnqualifiedVersionless();
try {
Patient p = new Patient();
p.getManagingOrganization().setReference(id1);
myPatientDao.create(p);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Invalid reference found at path 'Patient.managingOrganization'. Resource type 'Observation' is not valid for this path", e.getMessage());
}
try {
Patient p = new Patient();
p.getManagingOrganization().setReference(new IdDt("Organization", id1.getIdPart()));
myPatientDao.create(p);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("Resource contains reference to Organization/testCreateWithIllegalReference but resource with ID testCreateWithIllegalReference is actually of type Observation", e.getMessage());
}
}
@Test
public void testChoiceParamDate() {
Observation o2 = new Observation();

View File

@ -60,6 +60,12 @@
QuestionnaireResponse validator now allows responses to questions of
type OPENCHOICE to be of type 'string'
</action>
<action type="fix" issue="227">
JPA server should reject resources with a reference that points to an incorrectly typed
resource (e.g. points to Patient/123 but resource 123 is actually an Observation) or points
to a resource that is not valid in the location it is found in (e.g. points to Patient/123 but
the field supposed to reference an Organization). Thanks to Bill de Beaubien for reporting!
</action>
</release>
<release version="1.2" date="2015-09-18">
<action type="add">