Refactor to support arbitrary additional SPs based on Type for a compartment
This commit is contained in:
parent
b2d6f75964
commit
3d24a4ce16
|
@ -704,6 +704,21 @@ public class FhirTerser {
|
||||||
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
|
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
|
||||||
*/
|
*/
|
||||||
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
|
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget) {
|
||||||
|
return isSourceInCompartmentForTarget(theCompartmentName, theSource, theTarget, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns <code>true</code> if <code>theSource</code> is in the compartment named <code>theCompartmentName</code>
|
||||||
|
* belonging to resource <code>theTarget</code>
|
||||||
|
*
|
||||||
|
* @param theCompartmentName The name of the compartment
|
||||||
|
* @param theSource The potential member of the compartment
|
||||||
|
* @param theTarget The owner of the compartment. Note that both the resource type and ID must be filled in on this IIdType or the method will throw an {@link IllegalArgumentException}
|
||||||
|
* @param theAdditionalCompartmentParamNames If provided, search param names provided here will be considered as included in the given compartment for this comparison.
|
||||||
|
* @return <code>true</code> if <code>theSource</code> is in the compartment or one of the additional parameters matched.
|
||||||
|
* @throws IllegalArgumentException If theTarget does not contain both a resource type and ID
|
||||||
|
*/
|
||||||
|
public boolean isSourceInCompartmentForTarget(String theCompartmentName, IBaseResource theSource, IIdType theTarget, Set<String> theAdditionalCompartmentParamNames) {
|
||||||
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
|
Validate.notBlank(theCompartmentName, "theCompartmentName must not be null or blank");
|
||||||
Validate.notNull(theSource, "theSource must not be null");
|
Validate.notNull(theSource, "theSource must not be null");
|
||||||
Validate.notNull(theTarget, "theTarget must not be null");
|
Validate.notNull(theTarget, "theTarget must not be null");
|
||||||
|
@ -720,6 +735,18 @@ public class FhirTerser {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
|
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(theCompartmentName);
|
||||||
|
|
||||||
|
//If passed an additional set of searchparameter names, add them for comparison purposes.
|
||||||
|
if (theAdditionalCompartmentParamNames != null) {
|
||||||
|
List<RuntimeSearchParam> additionalParams = theAdditionalCompartmentParamNames.stream().map(paramName -> sourceDef.getSearchParam(paramName)).collect(Collectors.toList());
|
||||||
|
if (params == null || params.isEmpty()) {
|
||||||
|
params = additionalParams;
|
||||||
|
} else {
|
||||||
|
params.addAll(additionalParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (RuntimeSearchParam nextParam : params) {
|
for (RuntimeSearchParam nextParam : params) {
|
||||||
for (String nextPath : nextParam.getPathsSplit()) {
|
for (String nextPath : nextParam.getPathsSplit()) {
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,4 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
|
|
||||||
enum AppliesTypeEnum {
|
enum AppliesTypeEnum {
|
||||||
ALL_RESOURCES, TYPES, INSTANCES
|
ALL_RESOURCES, TYPES, INSTANCES
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
@ -58,6 +59,8 @@ public interface IAuthRuleBuilderRuleOpClassifier {
|
||||||
*/
|
*/
|
||||||
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners);
|
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners);
|
||||||
|
|
||||||
|
IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, IIdType theOwner, List<String> additionalTypeSearchParamNames);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rule applies to any resource instances
|
* Rule applies to any resource instances
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -451,6 +451,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
private Collection<? extends IIdType> myInCompartmentOwners;
|
private Collection<? extends IIdType> myInCompartmentOwners;
|
||||||
private Collection<IIdType> myAppliesToInstances;
|
private Collection<IIdType> myAppliesToInstances;
|
||||||
private RuleImplOp myRule;
|
private RuleImplOp myRule;
|
||||||
|
private List<String> myAdditionalSearchParamsForCompartmentTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -483,6 +484,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
myRule.setClassifierCompartmentOwners(myInCompartmentOwners);
|
myRule.setClassifierCompartmentOwners(myInCompartmentOwners);
|
||||||
myRule.setAppliesToDeleteCascade(myOnCascade);
|
myRule.setAppliesToDeleteCascade(myOnCascade);
|
||||||
myRule.setAppliesToDeleteExpunge(myOnExpunge);
|
myRule.setAppliesToDeleteExpunge(myOnExpunge);
|
||||||
|
myRule.setAdditionalSearchParamsForCompartmentTypes(myAdditionalSearchParamsForCompartmentTypes);
|
||||||
myRules.add(myRule);
|
myRules.add(myRule);
|
||||||
|
|
||||||
return new RuleBuilderFinished(myRule);
|
return new RuleBuilderFinished(myRule);
|
||||||
|
@ -519,6 +521,26 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
||||||
return finished();
|
return finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, IIdType theOwner, List<String> 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<RuleImplOp> 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<RuleImplOp> findMatchingRule() {
|
private Optional<RuleImplOp> findMatchingRule() {
|
||||||
return myRules.stream()
|
return myRules.stream()
|
||||||
.filter(RuleImplOp.class::isInstance)
|
.filter(RuleImplOp.class::isInstance)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import ca.uhn.fhir.util.UrlUtil;
|
||||||
import ca.uhn.fhir.util.bundle.BundleEntryParts;
|
import ca.uhn.fhir.util.bundle.BundleEntryParts;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
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.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
|
@ -28,6 +29,8 @@ import javax.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -69,6 +72,8 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
private Collection<IIdType> myAppliesToInstances;
|
private Collection<IIdType> myAppliesToInstances;
|
||||||
private boolean myAppliesToDeleteCascade;
|
private boolean myAppliesToDeleteCascade;
|
||||||
private boolean myAppliesToDeleteExpunge;
|
private boolean myAppliesToDeleteExpunge;
|
||||||
|
private boolean myDeviceIncludedInPatientCompartment;
|
||||||
|
private Map<String, Set<String>> myAdditionalCompartmentSearchParamMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
|
@ -337,7 +342,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
|
|
||||||
for (IIdType next : myClassifierCompartmentOwners) {
|
for (IIdType next : myClassifierCompartmentOwners) {
|
||||||
if (target.resource != null) {
|
if (target.resource != null) {
|
||||||
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next)) {
|
|
||||||
|
Set<String> additionalSearchParamNames = null;
|
||||||
|
if (myAdditionalCompartmentSearchParamMap != null) {
|
||||||
|
additionalSearchParamNames = myAdditionalCompartmentSearchParamMap.get(target.resourceType);
|
||||||
|
}
|
||||||
|
if (t.isSourceInCompartmentForTarget(myClassifierCompartmentName, target.resource, next, additionalSearchParamNames)) {
|
||||||
foundMatch = true;
|
foundMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -372,6 +382,12 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
String compartmentOwnerResourceType = next.getResourceType();
|
String compartmentOwnerResourceType = next.getResourceType();
|
||||||
if (!StringUtils.equals(target.resourceType, compartmentOwnerResourceType)) {
|
if (!StringUtils.equals(target.resourceType, compartmentOwnerResourceType)) {
|
||||||
List<RuntimeSearchParam> params = sourceDef.getSearchParamsForCompartmentName(compartmentOwnerResourceType);
|
List<RuntimeSearchParam> 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()) {
|
if (!params.isEmpty()) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -656,6 +672,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
myAppliesToDeleteExpunge = theAppliesToDeleteExpunge;
|
myAppliesToDeleteExpunge = theAppliesToDeleteExpunge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setDeviceIncludedInPatientCompartment(boolean theDeviceIncludedInPatientCompartment) {
|
||||||
|
myDeviceIncludedInPatientCompartment = theDeviceIncludedInPatientCompartment;
|
||||||
|
}
|
||||||
|
|
||||||
public void addClassifierCompartmentOwner(IIdType theOwner) {
|
public void addClassifierCompartmentOwner(IIdType theOwner) {
|
||||||
List<IIdType> newList = new ArrayList<>(myClassifierCompartmentOwners);
|
List<IIdType> newList = new ArrayList<>(myClassifierCompartmentOwners);
|
||||||
newList.add(theOwner);
|
newList.add(theOwner);
|
||||||
|
@ -681,4 +701,17 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAdditionalSearchParamsForCompartmentTypes(List<String> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,7 +376,39 @@ public class AuthorizationInterceptorR4Test {
|
||||||
assertTrue(ourHitMethod);
|
assertTrue(ourHitMethod);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@Test
|
||||||
|
public void testDeviceIsPartOfPatientCompartment() throws Exception {
|
||||||
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@Override
|
||||||
|
public List<IAuthRule> buildRuleList(RequestDetails theRequestDetails) {
|
||||||
|
List<String> 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
|
@Test
|
||||||
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
|
public void testAllowByCompartmentUsingUnqualifiedIds() throws Exception {
|
||||||
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
ourServlet.registerInterceptor(new AuthorizationInterceptor(PolicyEnum.DENY) {
|
||||||
|
@ -437,6 +469,24 @@ public class AuthorizationInterceptorR4Test {
|
||||||
extractResponseAndClose(status);
|
extractResponseAndClose(status);
|
||||||
assertEquals(403, status.getStatusLine().getStatusCode());
|
assertEquals(403, status.getStatusLine().getStatusCode());
|
||||||
assertTrue(ourHitMethod);
|
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<Resource> search(
|
||||||
|
@OptionalParam(name = "patient") ReferenceParam thePatient
|
||||||
|
) {
|
||||||
|
ourHitMethod = true;
|
||||||
|
return ourReturn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static class DummyObservationResourceProvider implements IResourceProvider {
|
public static class DummyObservationResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@ -3953,12 +4027,13 @@ public class AuthorizationInterceptorR4Test {
|
||||||
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
|
DummyEncounterResourceProvider encProv = new DummyEncounterResourceProvider();
|
||||||
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
|
DummyCarePlanResourceProvider cpProv = new DummyCarePlanResourceProvider();
|
||||||
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
|
DummyDiagnosticReportResourceProvider drProv = new DummyDiagnosticReportResourceProvider();
|
||||||
|
DummyDeviceResourceProvider devProv = new DummyDeviceResourceProvider();
|
||||||
PlainProvider plainProvider = new PlainProvider();
|
PlainProvider plainProvider = new PlainProvider();
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
ourServlet = new RestfulServer(ourCtx);
|
ourServlet = new RestfulServer(ourCtx);
|
||||||
ourServlet.setFhirContext(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 DummyServiceRequestResourceProvider());
|
||||||
ourServlet.registerProvider(new DummyConsentResourceProvider());
|
ourServlet.registerProvider(new DummyConsentResourceProvider());
|
||||||
ourServlet.setPlainProviders(plainProvider);
|
ourServlet.setPlainProviders(plainProvider);
|
||||||
|
|
Loading…
Reference in New Issue