mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-03-28 10:58:47 +00:00
Fix compartment defs for R5 (#2528)
* Fix compartment defs for R5 * Add changelog * Test fix
This commit is contained in:
parent
7cf061d76b
commit
462d9bc6c4
hapi-fhir-base/src/main/java/ca/uhn/fhir/context
hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0
hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa
@ -187,6 +187,11 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
|
||||
for (RuntimeSearchParam next : searchParams) {
|
||||
if (next.getProvidesMembershipInCompartments() != null) {
|
||||
for (String nextCompartment : next.getProvidesMembershipInCompartments()) {
|
||||
|
||||
if (nextCompartment.startsWith("Base FHIR compartment definition for ")) {
|
||||
nextCompartment = nextCompartment.substring("Base FHIR compartment definition for ".length());
|
||||
}
|
||||
|
||||
if (!compartmentNameToSearchParams.containsKey(nextCompartment)) {
|
||||
compartmentNameToSearchParams.put(nextCompartment, new ArrayList<>());
|
||||
}
|
||||
|
5
hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2528-fix-r5-compartment-defs.yaml
Normal file
5
hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2528-fix-r5-compartment-defs.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
type: fix
|
||||
issue: 2528
|
||||
title: "An issue with compartment definitions in R5 models was fixed. This issue caused some authorization
|
||||
rules to reject valid requests. Thanks to Patrick Palacin for reporting!"
|
@ -49,7 +49,6 @@ import ca.uhn.fhir.jpa.entity.TermValueSet;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetConcept;
|
||||
import ca.uhn.fhir.jpa.interceptor.PerformanceTracingLoggingInterceptor;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
@ -61,7 +60,6 @@ import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry;
|
||||
import ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.ValueSetExpansionR4Test;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc;
|
||||
import ca.uhn.fhir.jpa.term.api.ITermReadSvcR5;
|
||||
@ -73,6 +71,7 @@ import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.BasePagingProvider;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
|
||||
import ca.uhn.fhir.test.utilities.ITestDataBuilder;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import ca.uhn.fhir.validation.FhirValidator;
|
||||
import ca.uhn.fhir.validation.ValidationResult;
|
||||
@ -81,6 +80,7 @@ import org.hibernate.search.mapper.orm.Search;
|
||||
import org.hibernate.search.mapper.orm.session.SearchSession;
|
||||
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r5.model.AllergyIntolerance;
|
||||
import org.hl7.fhir.r5.model.Appointment;
|
||||
import org.hl7.fhir.r5.model.AuditEvent;
|
||||
@ -164,10 +164,9 @@ import static org.mockito.Mockito.mock;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {TestR5Config.class})
|
||||
public abstract class BaseJpaR5Test extends BaseJpaTest {
|
||||
public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuilder {
|
||||
private static IValidationSupport ourJpaValidationSupportChainR5;
|
||||
private static IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> ourValueSetDao;
|
||||
|
||||
@Autowired
|
||||
protected ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc;
|
||||
@Autowired
|
||||
@ -416,6 +415,23 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
|
||||
@Autowired
|
||||
private IBulkDataExportSvc myBulkDataExportSvc;
|
||||
|
||||
@Override
|
||||
public IIdType doCreateResource(IBaseResource theResource) {
|
||||
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
|
||||
return dao.create(theResource, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIdType doUpdateResource(IBaseResource theResource) {
|
||||
IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass());
|
||||
return dao.update(theResource, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FhirContext getFhirContext() {
|
||||
return myFhirCtx;
|
||||
}
|
||||
|
||||
@AfterEach()
|
||||
public void afterCleanupDao() {
|
||||
myDaoConfig.setExpireSearchResults(new DaoConfig().isExpireSearchResults());
|
||||
@ -463,7 +479,7 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
|
||||
@BeforeEach
|
||||
public void beforeFlushFT() {
|
||||
runInTransaction(() -> {
|
||||
SearchSession searchSession = Search.session(myEntityManager);
|
||||
SearchSession searchSession = Search.session(myEntityManager);
|
||||
searchSession.workspace(ResourceTable.class).purge();
|
||||
// searchSession.workspace(ResourceIndexedSearchParamString.class).purge();
|
||||
searchSession.indexingPlan().execute();
|
||||
@ -524,6 +540,43 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
|
||||
dao.update(resourceParsed);
|
||||
}
|
||||
|
||||
protected ValueSet.ValueSetExpansionContainsComponent assertExpandedValueSetContainsConcept(ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Integer theDesignationCount) {
|
||||
List<ValueSet.ValueSetExpansionContainsComponent> contains = theValueSet.getExpansion().getContains();
|
||||
|
||||
Stream<ValueSet.ValueSetExpansionContainsComponent> stream = contains.stream();
|
||||
if (theSystem != null) {
|
||||
stream = stream.filter(concept -> theSystem.equalsIgnoreCase(concept.getSystem()));
|
||||
}
|
||||
if (theCode != null) {
|
||||
stream = stream.filter(concept -> theCode.equalsIgnoreCase(concept.getCode()));
|
||||
}
|
||||
if (theDisplay != null) {
|
||||
stream = stream.filter(concept -> theDisplay.equalsIgnoreCase(concept.getDisplay()));
|
||||
}
|
||||
if (theDesignationCount != null) {
|
||||
stream = stream.filter(concept -> concept.getDesignation().size() == theDesignationCount);
|
||||
}
|
||||
|
||||
Optional<ValueSet.ValueSetExpansionContainsComponent> first = stream.findFirst();
|
||||
if (!first.isPresent()) {
|
||||
String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount);
|
||||
fail(failureMessage);
|
||||
return null;
|
||||
} else {
|
||||
return first.get();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getExpandedConceptsByValueSetUrl(String theValuesetUrl) {
|
||||
return runInTransaction(() -> {
|
||||
List<TermValueSet> valueSets = myTermValueSetDao.findTermValueSetByUrl(Pageable.unpaged(), theValuesetUrl);
|
||||
assertEquals(1, valueSets.size());
|
||||
TermValueSet valueSet = valueSets.get(0);
|
||||
List<TermValueSetConcept> concepts = valueSet.getConcepts();
|
||||
return concepts.stream().map(concept -> concept.getCode()).collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClassClearContextBaseJpaR5Test() {
|
||||
ourValueSetDao.purgeCaches();
|
||||
@ -634,40 +687,5 @@ public abstract class BaseJpaR5Test extends BaseJpaTest {
|
||||
String[] uuidParams = params.get(Constants.PARAM_PAGINGACTION);
|
||||
return uuidParams[0];
|
||||
}
|
||||
protected ValueSet.ValueSetExpansionContainsComponent assertExpandedValueSetContainsConcept(ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Integer theDesignationCount) {
|
||||
List<ValueSet.ValueSetExpansionContainsComponent> contains = theValueSet.getExpansion().getContains();
|
||||
|
||||
Stream<ValueSet.ValueSetExpansionContainsComponent> stream = contains.stream();
|
||||
if (theSystem != null) {
|
||||
stream = stream.filter(concept -> theSystem.equalsIgnoreCase(concept.getSystem()));
|
||||
}
|
||||
if (theCode != null ) {
|
||||
stream = stream.filter(concept -> theCode.equalsIgnoreCase(concept.getCode()));
|
||||
}
|
||||
if (theDisplay != null){
|
||||
stream = stream.filter(concept -> theDisplay.equalsIgnoreCase(concept.getDisplay()));
|
||||
}
|
||||
if (theDesignationCount != null) {
|
||||
stream = stream.filter(concept -> concept.getDesignation().size() == theDesignationCount);
|
||||
}
|
||||
|
||||
Optional<ValueSet.ValueSetExpansionContainsComponent> first = stream.findFirst();
|
||||
if (!first.isPresent()) {
|
||||
String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount);
|
||||
fail(failureMessage);
|
||||
return null;
|
||||
} else {
|
||||
return first.get();
|
||||
}
|
||||
}
|
||||
public List<String> getExpandedConceptsByValueSetUrl(String theValuesetUrl) {
|
||||
return runInTransaction(() -> {
|
||||
List<TermValueSet> valueSets = myTermValueSetDao.findTermValueSetByUrl(Pageable.unpaged(), theValuesetUrl);
|
||||
assertEquals(1, valueSets.size());
|
||||
TermValueSet valueSet = valueSets.get(0);
|
||||
List<TermValueSetConcept> concepts = valueSet.getConcepts();
|
||||
return concepts.stream().map(concept -> concept.getCode()).collect(Collectors.toList());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
163
hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/AuthorizationInterceptorJpaR5Test.java
Normal file
163
hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r5/AuthorizationInterceptorJpaR5Test.java
Normal file
@ -0,0 +1,163 @@
|
||||
package ca.uhn.fhir.jpa.provider.r5;
|
||||
|
||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||
import ca.uhn.fhir.jpa.provider.r5.BaseResourceProviderR5Test;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRuleTester;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum;
|
||||
import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder;
|
||||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
import org.hl7.fhir.r5.model.CodeableConcept;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.Condition;
|
||||
import org.hl7.fhir.r5.model.Encounter;
|
||||
import org.hl7.fhir.r5.model.Enumerations;
|
||||
import org.hl7.fhir.r5.model.IdType;
|
||||
import org.hl7.fhir.r5.model.Identifier;
|
||||
import org.hl7.fhir.r5.model.Observation;
|
||||
import org.hl7.fhir.r5.model.Organization;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.Patient;
|
||||
import org.hl7.fhir.r5.model.Practitioner;
|
||||
import org.hl7.fhir.r5.model.Reference;
|
||||
import org.hl7.fhir.r5.model.StringType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class AuthorizationInterceptorJpaR5Test extends BaseResourceProviderR5Test {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptorJpaR5Test.class);
|
||||
|
||||
@BeforeEach
|
||||
@Override
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
myDaoConfig.setExpungeEnabled(true);
|
||||
myDaoConfig.setDeleteExpungeEnabled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* See #503
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteIsAllowedForCompartment() {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
|
||||
patient.addName().setFamily("Tester").addGiven("Raghad");
|
||||
final IIdType id = myClient.create().resource(patient).execute().getId();
|
||||
|
||||
Observation obsInCompartment = new Observation();
|
||||
obsInCompartment.setStatus(Enumerations.ObservationStatus.FINAL);
|
||||
obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless());
|
||||
IIdType obsInCompartmentId = myClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obsNotInCompartment = new Observation();
|
||||
obsNotInCompartment.setStatus(Enumerations.ObservationStatus.FINAL);
|
||||
IIdType obsNotInCompartmentId = myClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen()
|
||||
.deny().delete().allResources().withAnyId().andThen()
|
||||
.allowAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
myClient.delete().resourceById(obsInCompartmentId.toUnqualifiedVersionless()).execute();
|
||||
|
||||
try {
|
||||
myClient.delete().resourceById(obsNotInCompartmentId.toUnqualifiedVersionless()).execute();
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteIsAllowedForCompartmentUsingTransaction() {
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("http://uhn.ca/mrns").setValue("100");
|
||||
patient.addName().setFamily("Tester").addGiven("Raghad");
|
||||
final IIdType id = myClient.create().resource(patient).execute().getId();
|
||||
|
||||
Observation obsInCompartment = new Observation();
|
||||
obsInCompartment.setStatus(Enumerations.ObservationStatus.FINAL);
|
||||
obsInCompartment.getSubject().setReferenceElement(id.toUnqualifiedVersionless());
|
||||
IIdType obsInCompartmentId = myClient.create().resource(obsInCompartment).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
Observation obsNotInCompartment = new Observation();
|
||||
obsNotInCompartment.setStatus(Enumerations.ObservationStatus.FINAL);
|
||||
IIdType obsNotInCompartmentId = myClient.create().resource(obsNotInCompartment).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
ourRestServer.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||
@Override
|
||||
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||
return new RuleBuilder()
|
||||
.allow().delete().resourcesOfType(Observation.class).inCompartment("Patient", id).andThen()
|
||||
.allow().transaction().withAnyOperation().andApplyNormalRules().andThen()
|
||||
.denyAll()
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
Bundle bundle;
|
||||
|
||||
bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(obsInCompartmentId.toUnqualifiedVersionless().getValue());
|
||||
myClient.transaction().withBundle(bundle).execute();
|
||||
|
||||
try {
|
||||
bundle = new Bundle();
|
||||
bundle.setType(Bundle.BundleType.TRANSACTION);
|
||||
bundle.addEntry().getRequest().setMethod(Bundle.HTTPVerb.DELETE).setUrl(obsNotInCompartmentId.toUnqualifiedVersionless().getValue());
|
||||
myClient.transaction().withBundle(bundle).execute();
|
||||
fail();
|
||||
} catch (ForbiddenOperationException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user