additionalTypeSearchParamNames);
+
/**
* Rule applies to any resource instances
*
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java
index 086797a8ef7..4e1c9b8ecd3 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java
@@ -451,6 +451,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
private Collection extends IIdType> myInCompartmentOwners;
private Collection myAppliesToInstances;
private RuleImplOp myRule;
+ private List myAdditionalSearchParamsForCompartmentTypes;
/**
* Constructor
@@ -483,6 +484,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
myRule.setClassifierCompartmentOwners(myInCompartmentOwners);
myRule.setAppliesToDeleteCascade(myOnCascade);
myRule.setAppliesToDeleteExpunge(myOnExpunge);
+ myRule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
myRules.add(myRule);
return new RuleBuilderFinished(myRule);
@@ -519,6 +521,26 @@ public class RuleBuilder implements IAuthRuleBuilder {
return finished();
}
+ @Override
+ public IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, IIdType theOwner, List additionalTypeSearchParamNames) {
+ Validate.notBlank(theCompartmentName, "theCompartmentName must not be null");
+ Validate.notNull(theOwner, "theOwner must not be null");
+ validateOwner(theOwner);
+ myClassifierType = ClassifierTypeEnum.IN_COMPARTMENT;
+ myInCompartmentName = theCompartmentName;
+ Optional oRule = findMatchingRule();
+
+ if (oRule.isPresent()) {
+ RuleImplOp rule = oRule.get();
+ rule.setAdditionalSearchParamsForCompartmentTypes(additionalTypeSearchParamNames);
+ rule.addClassifierCompartmentOwner(theOwner);
+ return new RuleBuilderFinished(rule);
+ }
+ myInCompartmentOwners = Collections.singletonList(theOwner);
+ return finished();
+ }
+
+
private Optional findMatchingRule() {
return myRules.stream()
.filter(RuleImplOp.class::isInstance)
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java
index 0810235f2ac..73ccec7d447 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java
@@ -18,6 +18,7 @@ import ca.uhn.fhir.util.UrlUtil;
import ca.uhn.fhir.util.bundle.BundleEntryParts;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@@ -28,6 +29,8 @@ import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -69,6 +72,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
private Collection myAppliesToInstances;
private boolean myAppliesToDeleteCascade;
private boolean myAppliesToDeleteExpunge;
+ private boolean myDeviceIncludedInPatientCompartment;
+ private Map> myAdditionalCompartmentSearchParamMap;
/**
* Constructor
@@ -337,7 +342,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
for (IIdType next : myClassifierCompartmentOwners) {
if (target.resource != null) {
- if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next)) {
+
+ Set additionalSearchParamNames = null;
+ if (myAdditionalCompartmentSearchParamMap != null) {
+ additionalSearchParamNames = myAdditionalCompartmentSearchParamMap.get(target.resourceType);
+ }
+ if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next, additionalSearchParamNames)) {
foundMatch = true;
break;
}
@@ -372,6 +382,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
String compartmentOwnerResourceType = next.getResourceType();
if (!StringUtils.equals(target.resourceType, compartmentOwnerResourceType)) {
List params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
+ if (target.resourceType.equalsIgnoreCase("Device") && myDeviceIncludedInPatientCompartment) {
+ if (params == null || params.isEmpty()) {
+ params = new ArrayList<>();
+ }
+ params.add(sourceDef.getSearchParam("patient"));
+ }
if (!params.isEmpty()) {
/*
@@ -656,6 +672,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
myAppliesToDeleteExpunge = theAppliesToDeleteExpunge;
}
+ void setDeviceIncludedInPatientCompartment(boolean theDeviceIncludedInPatientCompartment) {
+ myDeviceIncludedInPatientCompartment = theDeviceIncludedInPatientCompartment;
+ }
+
public void addClassifierCompartmentOwner(IIdType theOwner) {
List newList = new ArrayList<>(myClassifierCompartmentOwners);
newList.add(theOwner);
@@ -681,4 +701,17 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
return false;
}
}
+
+ public void setAdditionalSearchParamsForCompartmentTypes(List theTypeAndParams) {
+ if (myAdditionalCompartmentSearchParamMap == null) {
+ myAdditionalCompartmentSearchParamMap = new HashMap<>();
+ }
+
+ for (String typeAndParam: theTypeAndParams) {
+ String[] split = typeAndParam.split(",");
+ Validate.isTrue(split.length == 2);
+ myAdditionalCompartmentSearchParamMap.computeIfAbsent(split[0], (v) -> new HashSet<>()).add(split[1]);
+ }
+ this.myAdditionalCompartmentSearchParamMap = myAdditionalCompartmentSearchParamMap;
+ }
}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
index 3bb305e4211..958005cfa4a 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptorR4Test.java
@@ -376,7 +376,39 @@ public class AuthorizationInterceptorR4Test {
assertTrue(ourHitMethod);
}
+ @Test
+ public void testDeviceIsPartOfPatientCompartment() throws Exception {
+ ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
+ @Override
+ public List buildRuleList(RequestDetails theRequestDetails) {
+ List bonusPatientCompartmentSearchParams = Collections.singletonList("device:patient");
+ return new RuleBuilder()
+ .allow().read().allResources()
+ .inCompartmentWithAdditionalSearchParams("Patient", new IdType("Patient/123"), bonusPatientCompartmentSearchParams)
+ .andThen().denyAll()
+ .build();
+ }
+ });
+ HttpGet httpGet;
+ HttpResponse status;
+
+ Patient patient;
+
+
+ patient = new Patient();
+ patient.setId("Patient/123");
+ Device d = new Device();
+ d.getPatient().setResource(patient);
+
+ ourHitMethod = false;
+ ourReturn = Collections.singletonList(d);
+ httpGet = new HttpGet("http://localhost:" + ourPort + "/Device/124456");
+ status = ourClient.execute(httpGet);
+ extractResponseAndClose(status);
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ assertTrue(ourHitMethod);
+ }
@Test
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
@@ -437,6 +469,24 @@ public class AuthorizationInterceptorR4Test {
extractResponseAndClose(status);
assertEquals(403, status.getStatusLine().getStatusCode());
assertTrue(ourHitMethod);
+
+
+ patient = new Patient();
+ patient.setId("Patient/123");
+ carePlan = new CarePlan();
+ carePlan.setStatus(CarePlan.CarePlanStatus.ACTIVE);
+ carePlan.getSubject().setResource(patient);
+
+ Device d = new Device();
+ d.getPatient().setResource(patient);
+
+ ourHitMethod = false;
+ ourReturn = Collections.singletonList(d);
+ httpGet = new HttpGet("http://localhost:" + ourPort + "/Device/123456");
+ status = ourClient.execute(httpGet);
+ extractResponseAndClose(status);
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ assertTrue(ourHitMethod);
}
/**
@@ -3619,6 +3669,30 @@ public class AuthorizationInterceptorR4Test {
}
}
+ public static class DummyDeviceResourceProvider implements IResourceProvider {
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Device.class;
+ }
+
+ @Read(version = true)
+ public Device read(@IdParam IdType theId) {
+ ourHitMethod = true;
+ if (ourReturn.isEmpty()) {
+ throw new ResourceNotFoundException(theId);
+ }
+ return (Device) ourReturn.get(0);
+ }
+ @Search()
+ public List search(
+ @OptionalParam(name = "patient") ReferenceParam thePatient
+ ) {
+ ourHitMethod = true;
+ return ourReturn;
+ }
+ }
+
@SuppressWarnings("unused")
public static class DummyObservationResourceProvider implements IResourceProvider {
@@ -3953,12 +4027,13 @@ public class AuthorizationInterceptorR4Test {
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
+ DummyDeviceResourceProvider devProv = new DummyDeviceResourceProvider();
PlainProvider plainProvider = new PlainProvider();
ServletHandler proxyHandler = new ServletHandler();
ourServlet = new RestfulServer(ourCtx);
ourServlet.setFhirContext(ourCtx);
- ourServlet.registerProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv);
+ ourServlet.registerProviders(patProvider, obsProv, encProv, cpProv, orgProv, drProv, devProv);
ourServlet.registerProvider(new DummyServiceRequestResourceProvider());
ourServlet.registerProvider(new DummyConsentResourceProvider());
ourServlet.setPlainProviders(plainProvider);