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
|
||||
*/
|
||||
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.notNull(theSource, "theSource 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);
|
||||
|
||||
//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 (String nextPath : nextParam.getPathsSplit()) {
|
||||
|
||||
|
|
|
@ -22,7 +22,4 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
|
||||
enum AppliesTypeEnum {
|
||||
ALL_RESOURCES, TYPES, INSTANCES
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.auth;
|
|||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
|
@ -58,6 +59,8 @@ public interface IAuthRuleBuilderRuleOpClassifier {
|
|||
*/
|
||||
IAuthRuleBuilderRuleOpClassifierFinished inCompartment(String theCompartmentName, Collection<? extends IIdType> theOwners);
|
||||
|
||||
IAuthRuleBuilderRuleOpClassifierFinished inCompartmentWithAdditionalSearchParams(String theCompartmentName, IIdType theOwner, List<String> additionalTypeSearchParamNames);
|
||||
|
||||
/**
|
||||
* Rule applies to any resource instances
|
||||
* <p>
|
||||
|
|
|
@ -451,6 +451,7 @@ public class RuleBuilder implements IAuthRuleBuilder {
|
|||
private Collection<? extends IIdType> myInCompartmentOwners;
|
||||
private Collection<IIdType> myAppliesToInstances;
|
||||
private RuleImplOp myRule;
|
||||
private List<String> 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<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() {
|
||||
return myRules.stream()
|
||||
.filter(RuleImplOp.class::isInstance)
|
||||
|
|
|
@ -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<IIdType> myAppliesToInstances;
|
||||
private boolean myAppliesToDeleteCascade;
|
||||
private boolean myAppliesToDeleteExpunge;
|
||||
private boolean myDeviceIncludedInPatientCompartment;
|
||||
private Map<String, Set<String>> 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<String> 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<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()) {
|
||||
|
||||
/*
|
||||
|
@ -656,6 +672,10 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
myAppliesToDeleteExpunge = theAppliesToDeleteExpunge;
|
||||
}
|
||||
|
||||
void setDeviceIncludedInPatientCompartment(boolean theDeviceIncludedInPatientCompartment) {
|
||||
myDeviceIncludedInPatientCompartment = theDeviceIncludedInPatientCompartment;
|
||||
}
|
||||
|
||||
public void addClassifierCompartmentOwner(IIdType theOwner) {
|
||||
List<IIdType> newList = new ArrayList<>(myClassifierCompartmentOwners);
|
||||
newList.add(theOwner);
|
||||
|
@ -681,4 +701,17 @@ class RuleImplOp extends BaseRule /* implements IAuthRule */ {
|
|||
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);
|
||||
|
||||
}
|
||||
@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
|
||||
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<Resource> 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);
|
||||
|
|
Loading…
Reference in New Issue